aboutsummaryrefslogtreecommitdiff
path: root/hw/ast-bmc/ast-mctp.c
blob: c2efb7b27e9ddc26074913ce42befc561119d607 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
// Copyright 2022 IBM Corp.

#define pr_fmt(fmt) "AST-MCTP: " fmt

#include <lock.h>
#include <lpc.h>
#include <interrupts.h>
#include <timer.h>
#include <timebase.h>
#include <debug_descriptor.h>
#include <device.h>
#include <ast.h>
#include <console.h>
#include <libmctp.h>
#include <libmctp-cmds.h>
#include <libmctp-log.h>
#include <libmctp-astlpc.h>

static struct mctp *mctp;
static struct mctp_binding_astlpc *astlpc;
static struct astlpc_ops_data *ops_data;
static struct lock mctp_lock = LOCK_UNLOCKED;

/* Keyboard Controller Style (KCS) data register address */
#define KCS_DATA_REG 0xca2

/* Keyboard Controller Style (KCS) status register address */
#define KCS_STATUS_REG 0xca3

#define KCS_STATUS_BMC_READY 0x80
#define KCS_STATUS_OBF       0x01

#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
#define DESIRED_MTU 32768

#define TX_POLL_MAX 5

/*
 * The AST LPC binding is described here:
 *
 * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-ibm-astlpc.md
 *
 * Most of the binding is implemented in libmctp, but we need to provide
 * accessors for the LPC FW space (for the packet buffer) and for the KCS
 * peripheral (for the interrupt mechanism).
 */

struct astlpc_ops_data {
	uint16_t kcs_data_addr; /* LPC IO space offset for the data register */
	uint16_t kcs_stat_addr;

	/* address of the packet exchange buffer in FW space */
	uint32_t lpc_fw_addr;
};

static int astlpc_kcs_reg_read(void *binding_data,
			       enum mctp_binding_astlpc_kcs_reg reg,
			       uint8_t *val)
{
	struct astlpc_ops_data *ops_data = binding_data;
	uint32_t addr;

	if (reg == MCTP_ASTLPC_KCS_REG_STATUS)
		addr = ops_data->kcs_stat_addr;
	else if (reg == MCTP_ASTLPC_KCS_REG_DATA)
		addr = ops_data->kcs_data_addr;
	else
		return OPAL_PARAMETER;

	*val = lpc_inb(addr);

	return OPAL_SUCCESS;
}

static int astlpc_kcs_reg_write(void *binding_data,
				enum mctp_binding_astlpc_kcs_reg reg,
				uint8_t val)
{
	struct astlpc_ops_data *ops_data = binding_data;
	uint32_t addr;

	prlog(PR_TRACE, "%s 0x%hhx to %s\n",
			__func__, val, reg ? "status" : "data");

	if (reg == MCTP_ASTLPC_KCS_REG_STATUS)
		addr = ops_data->kcs_stat_addr;
	else if (reg == MCTP_ASTLPC_KCS_REG_DATA)
		addr = ops_data->kcs_data_addr;
	else
		return OPAL_PARAMETER;

	lpc_outb(val, addr);

	return OPAL_SUCCESS;
}

static int astlpc_read(void *binding_data, void *buf, long offset,
		       size_t len)
{
	struct astlpc_ops_data *ops_data = binding_data;

	prlog(PR_TRACE, "%s %zu bytes from 0x%lx (lpc: 0x%lx)\n",
			__func__, len, offset,
			ops_data->lpc_fw_addr + offset);
	return lpc_fw_read(ops_data->lpc_fw_addr + offset, buf, len);
}

static int astlpc_write(void *binding_data, const void *buf,
			long offset, size_t len)
{
	struct astlpc_ops_data *ops_data = binding_data;

	prlog(PR_TRACE, "%s %zu bytes to offset 0x%lx (lpc: 0x%lx)\n",
			__func__, len, offset,
			ops_data->lpc_fw_addr + offset);
	return lpc_fw_write(ops_data->lpc_fw_addr + offset, buf, len);
}

static const struct mctp_binding_astlpc_ops astlpc_ops = {
	.kcs_read = astlpc_kcs_reg_read,
	.kcs_write = astlpc_kcs_reg_write,
	.lpc_read = astlpc_read,
	.lpc_write = astlpc_write,
};

/* we need a poller to crank the mctp state machine during boot */
static void astlpc_poller(void *data)
{
	struct mctp_binding_astlpc *astlpc = (struct mctp_binding_astlpc *)data;

	if (astlpc)
		mctp_astlpc_poll(astlpc);
}

/* at runtime the interrupt should handle it */
static void astlpc_interrupt(uint32_t chip_id __unused,
			     uint32_t irq_msk __unused)
{
	if (astlpc)
		mctp_astlpc_poll(astlpc);
}

static struct lpc_client kcs_lpc_client = {
	.reset = NULL,
	.interrupt = astlpc_interrupt,
};

static void drain_odr(struct astlpc_ops_data *ops_data)
{
	uint8_t kcs_status, kcs_data;
	uint8_t drain_counter = 255;

	astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_STATUS, &kcs_status);

	while (--drain_counter && (kcs_status & KCS_STATUS_OBF)) {
		astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_DATA, &kcs_data);
		time_wait_ms(5);
		astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_STATUS, &kcs_status);
	}
}

static int astlpc_binding(void)
{
	struct mctp_bus *bus;
	int counter = 0;

	ops_data = zalloc(sizeof(struct astlpc_ops_data));
	if (!ops_data)
		return OPAL_NO_MEM;

	/*
	 * Current OpenBMC systems put the MCTP buffer 1MB down from
	 * the end of the LPC FW range.
	 *
	 * The size of the FW range is: 0x1000_0000 so the window be at:
	 *
	 *   0x1000_0000 - 2**20 == 0xff00000
	 */
	ops_data->lpc_fw_addr = 0xff00000;

	/* values chosen by the OpenBMC driver */
	ops_data->kcs_data_addr = KCS_DATA_REG;
	ops_data->kcs_stat_addr = KCS_STATUS_REG;

	/* Initialise the binding */
	astlpc = mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_HOST,
				  DESIRED_MTU,
				  NULL,
				  &astlpc_ops,
				  ops_data);
	if (!astlpc) {
		prlog(PR_ERR, "binding init failed\n");
		return OPAL_HARDWARE;
	}

	/* Read and discard any potentially stale messages in the ODR */
	drain_odr(ops_data);

	/* Register the binding to the LPC bus we are using for this
	 * MCTP configuration.
	 */
	if (mctp_register_bus(mctp,
			      mctp_binding_astlpc_core(astlpc),
			      HOST_EID)) {
		prlog(PR_ERR, "failed to register bus\n");
		goto err;
	}

	/* lpc/kcs status register poller */
	opal_add_poller(astlpc_poller, astlpc);

	/* Don't start sending messages to the BMC until the bus has
	 * been registered and tx has been enabled
	 */
	bus = mctp_binding_astlpc_core(astlpc)->bus;

	while ((bus == NULL) ||
	      (mctp_bus_get_state(bus) == mctp_bus_state_constructed)) {
		if (++counter >= 1000) {
			prlog(PR_ERR, "failed to initialize MCTP channel\n");
			goto err;
		}
		time_wait_ms(5);

		/* Update bus pointer if it is a nullptr */
		if (bus == NULL)
			bus = mctp_binding_astlpc_core(astlpc)->bus;
	}

	return OPAL_SUCCESS;

err:
	mctp_astlpc_destroy(astlpc);
	free(ops_data);

	return OPAL_HARDWARE;
}

static void *mctp_malloc(size_t size) { return malloc(size); }
static void mctp_free(void *ptr) { return free(ptr); }
static void *mctp_realloc(void *ptr, size_t size)
{
	return realloc(ptr, size);
}

#ifdef AST_MCTP_DEBUG
char buffer[320];
static void mctp_log(int log_lvl, const char *fmt, va_list va)
{
	snprintf(buffer, sizeof(buffer), "%s\n", fmt);
	vprlog(log_lvl, buffer, va);
}
#endif

int ast_mctp_message_tx(bool tag_owner, uint8_t msg_tag,
			uint8_t *msg, int msg_len)
{
	unsigned long stop_time;
	int rc = OPAL_SUCCESS;

	lock(&mctp_lock);

	rc = mctp_message_tx(mctp, BMC_EID, tag_owner, msg_tag,
			     msg, msg_len);
	unlock(&mctp_lock);

	/* do not poll when we respond to a BMC request */
	if (tag_owner)
		return rc;

	/* read the Rx_complete command out of the ODR */
	stop_time = mftb() + msecs_to_tb(TX_POLL_MAX);
	while (mftb() < stop_time && !mctp_astlpc_tx_done(astlpc))
		mctp_astlpc_poll(astlpc);

	return rc;
}

static void message_rx(uint8_t eid, bool tag_owner,
		       uint8_t msg_tag, void *data __unused,
		       void *vmsg, size_t len)
{
	uint8_t *msg = (uint8_t *)vmsg;

	prlog(PR_TRACE, "message received: msg type: %x, len %zd"
			" (eid: %d), rx tag %d owner %d\n",
			 *msg, len, eid, tag_owner, msg_tag);
}

/*
 * Initialize mctp binding for hbrt and provide interfaces for sending
 * and receiving mctp messages.
 */
int ast_mctp_init(void)
{
	uint32_t kcs_serial_irq;
	struct dt_node *n;

	/* Search mctp node */
	n = dt_find_compatible_node(dt_root, NULL, "mctp");
	if (!n) {
		prlog(PR_ERR, "No MCTP device\n");
		return OPAL_PARAMETER;
	}

	/* skiboot's malloc/free/realloc are macros so they need
	 * wrappers
	 */
	mctp_set_alloc_ops(mctp_malloc, mctp_free, mctp_realloc);

	/*
	 * /-----\                                        /---------\
	 * | bmc | (eid: 8) <- lpc pcie / kcs -> (eid: 9) | skiboot |
	 * \-----/                                        \---------/
	 */
	mctp = mctp_init();
	if (!mctp) {
		prlog(PR_ERR, "mctp init failed\n");
		return OPAL_HARDWARE;
	}

#ifdef AST_MCTP_DEBUG
	/* Setup the trace hook */
	mctp_set_log_custom(mctp_log);
#endif

	/* Set the max message size to be large enough */
	mctp_set_max_message_size(mctp, HOST_MAX_INCOMING_MESSAGE_ALLOCATION);

	/* Setup the message rx callback */
	mctp_set_rx_all(mctp, message_rx, NULL);

	/* Initialize the binding */
	if (astlpc_binding())
		goto err;

	/* register an lpc client so we get an interrupt */
	kcs_serial_irq = dt_prop_get_u32(n, "interrupts");
	kcs_lpc_client.interrupts = LPC_IRQ(kcs_serial_irq);
	lpc_register_client(dt_get_chip_id(n), &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);

	return OPAL_SUCCESS;

err:
	prlog(PR_ERR, "Unable to initialize MCTP\n");
	mctp_destroy(mctp);
	mctp = NULL;

	return OPAL_HARDWARE;
}

void ast_mctp_exit(void)
{
	if (astlpc) {
		mctp_astlpc_destroy(astlpc);
		astlpc = NULL;
	}

	if (mctp) {
		mctp_destroy(mctp);
		mctp = NULL;
	}
}