summaryrefslogtreecommitdiff
path: root/chip/g/sps_hc.c
blob: 1fe9c6f62a0b0c05550a2dfdbc0d57270f193a01 (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
/* Copyright 2015 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "common.h"
#include "console.h"
#include "hooks.h"
#include "host_command.h"
#include "sps.h"
#include "util.h"

/*
 * This implements EC host commands over the SPI bus, using the Cr50 SPS (SPI
 * slave) controller.
 *
 * Host commands are communicated using software flow-control, because most of
 * the embedded controllers either aren't fast enough or don't have any support
 * for hardware flow-control.
 *
 * Every SPI transaction is bi-directional, so when the AP sends commands to
 * the EC, a default "dummy" byte is returned at the same time. The EC
 * preconfigures that default response byte to indicate its status (ready,
 * busy, waiting for more input, etc). Once the AP has sent a complete command
 * message, it continues clocking bytes to the EC (which the EC ignores) and
 * just looks at the response byte that comes back. Once the EC has parsed the
 * command and is ready to reply, it sends a "start of frame" byte, followed by
 * the actual response. The AP continues to read and ignore bytes from the EC
 * until it sees the start of frame byte, and then it knows that the EC's
 * response is starting with the next byte. Refer to include/ec_commands.h for
 * more details.
 */

/* Console output macros */
#define CPUTS(outstr) cputs(CC_HOSTCMD, outstr)
#define CPRINTS(format, args...) cprints(CC_HOSTCMD, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_HOSTCMD, format, ## args)

/*
 * Incoming messages are collected here until they're ready to process. The
 * buffer will start with a struct ec_host_request, followed by whatever data
 * is sent by the master.
 */
#define RXBUF_MAX 512				/* chosen arbitrarily */
static uint8_t rxbuf[RXBUF_MAX];
static unsigned int rxbuf_count;		/* num bytes received */
static unsigned int rxbuf_needed;		/* num bytes we'd like */

/*
 * Outgoing messages are shoved in here. We need a preamble byte to mark the
 * start of the data stream before the data itself.
 */
#define TXBUF_MAX 512				/* chosen arbitrarily */
static uint8_t txbuf[1 + TXBUF_MAX];

static enum spi_state {
	/* SPI not enabled (initial state, and when chipset is off) */
	SPI_STATE_DISABLED = 0,

	/* Ready to receive next request */
	SPI_STATE_READY_TO_RX,

	/* Receiving request */
	SPI_STATE_RECEIVING_HEADER,
	SPI_STATE_RECEIVING_BODY,

	/* Processing request */
	SPI_STATE_PROCESSING,

	/* Sending response */
	SPI_STATE_SENDING,

	/*
	 * Received bad data - transaction started before we were ready, or
	 * packet header from the master didn't parse properly. Ignoring
	 * received data.
	 */
	SPI_STATE_RX_BAD,
} state;

/* If the chip select is disabled, don't transmit response */
static int discard_response;

/* Callback to send our response to the master */
static void cb_send_response_packet(struct host_packet *pkt)
{
	uint8_t *bufptr;
	size_t data_size = pkt->response_size;

	/* Chip Select was deasserted before we got here */
	if (discard_response) {
		discard_response = 0;
		state = SPI_STATE_READY_TO_RX;
		sps_tx_status(EC_SPI_RX_READY);
		return;
	}

	/*
	 * The pkt->response will start at txbuf[1], which means that
	 * pkt->response_size doesn't include the preamble byte. We need to
	 * initialize it and start sending from there.
	 */
	txbuf[0] = EC_SPI_FRAME_START;
	data_size++;
	bufptr = txbuf;
	state = SPI_STATE_SENDING;

	/* There's probably still a race condition somewhere... */
	while (data_size && state == SPI_STATE_SENDING) {
		size_t cnt = sps_transmit(bufptr, data_size);
		data_size -= cnt;
		bufptr += cnt;
	}

	/* Clock out the end-of-packet marker when we're done. */
	sps_tx_status(EC_SPI_PAST_END);
}

static int req_header_looks_good(const struct ec_host_request *req)
{
	return (req->struct_version == EC_HOST_REQUEST_VERSION) &&
		(req->reserved == 0) &&
		(sizeof(*req) + req->data_len <= RXBUF_MAX);
}

/* RX FIFO handler (runs in interrupt context) */
static void hc_rx_handler(uint8_t *data, size_t data_size, int cs_disabled)
{
	struct ec_host_request *req = (struct ec_host_request *)rxbuf;
	static struct host_packet rx_packet;
	size_t i;

	if (cs_disabled) {
		if (state == SPI_STATE_PROCESSING) {
			/*
			 * A task is preparing a response, but the master has
			 * stopped caring. Set a flag so when the response is
			 * ready we'll just throw it away and reset everything.
			 */
			discard_response = 1;
			return;
		}

		/* Otherwise, just go back to waiting for new input */
		state = SPI_STATE_READY_TO_RX;
		sps_tx_status(EC_SPI_RX_READY);
		return;
	}

	/* No data == nothing to do */
	if (!data_size)
		return;

	switch (state) {
	case SPI_STATE_READY_TO_RX:
		/* Starting a new RX transaction. */
		rxbuf_count = 0;
		state = SPI_STATE_RECEIVING_HEADER;
		sps_tx_status(EC_SPI_RECEIVING);
		/* Need a header first (proto v3 only) */
		rxbuf_needed = sizeof(*req);
		break;

	case SPI_STATE_RECEIVING_HEADER:
	case SPI_STATE_RECEIVING_BODY:
		/* still gathering bytes */
		break;

	case SPI_STATE_DISABLED:
		/*
		 * The master started a transaction but we weren't ready for it.
		 * Tell it we weren't ready, and ignore the incoming data until
		 * the master gives up.
		 */
		CPRINTS("SPI not ready (in state %d)", state);
		sps_tx_status(EC_SPI_NOT_READY);
		state = SPI_STATE_RX_BAD;
		return;

	default:
		/* Anything else doesn't need us to look at the input */
		return;
	}

	/* Collect incoming bytes */
	if (rxbuf_count + data_size > RXBUF_MAX)
		goto spi_event_error;
	memcpy(rxbuf + rxbuf_count, data, data_size);
	rxbuf_count += data_size;

	/* Wait until we have enough */
	if (rxbuf_count < rxbuf_needed)
		return;

	switch (state) {
	case SPI_STATE_RECEIVING_HEADER:
		/* Got the header - is it okay? */
		if (!req_header_looks_good(req))
			goto spi_event_error;

		/* Yep, now need the body */
		state = SPI_STATE_RECEIVING_BODY;
		rxbuf_needed += req->data_len;

		/* Still need more bytes? */
		if (rxbuf_count < rxbuf_needed)
			return;

		/* FALLTHROUGH */

	case SPI_STATE_RECEIVING_BODY:
		/* We have all the data we need. */
		state = SPI_STATE_PROCESSING;
		sps_tx_status(EC_SPI_PROCESSING);

		/* Hand it off to the host command task for processing */
		rx_packet.send_response = cb_send_response_packet;
		rx_packet.request = rxbuf;
		rx_packet.request_temp = NULL;
		rx_packet.request_max = RXBUF_MAX;
		rx_packet.request_size = rxbuf_count;
		rx_packet.response = txbuf + 1;	/* skip preamble byte */
		rx_packet.response_max = TXBUF_MAX;
		rx_packet.response_size = 0;
		rx_packet.driver_result = EC_RES_SUCCESS;
		host_packet_receive(&rx_packet);
		break;

	default:
		/* shouldn't get here */
		goto spi_event_error;
	}

	return;

 spi_event_error:
	/* Error, timeout, or protocol we can't handle.  Ignore data. */
	sps_tx_status(EC_SPI_RX_BAD_DATA);
	state = SPI_STATE_RX_BAD;

	CPRINTS("SPI RX BAD DATA");
	CPRINTF("[rxbuf (%d): ", rxbuf_count);
	for (i = 0; i < rxbuf_count; i++)
		CPRINTF("%02x ", rxbuf[i]);
	CPRINTF("]\n");
}

static void sps_hc_enable(void)
{
	/* I'm not listening, la la la la la ... */
	sps_tx_status(EC_SPI_NOT_READY);

	/* We are no longer actively processing a transaction */
	state = SPI_STATE_DISABLED;

	/* Ready to receive */
	sps_register_rx_handler(SPS_GENERIC_MODE, hc_rx_handler, 0);

	/* Here we go */
	discard_response = 0;
	state = SPI_STATE_READY_TO_RX;
	sps_tx_status(EC_SPI_RX_READY);
}

static void sps_hc_disable(void)
{
	sps_unregister_rx_handler();

	/* We are no longer actively processing a transaction */
	state = SPI_STATE_DISABLED;
}

static int sps_get_protocol_info(struct host_cmd_handler_args *args)
{
	struct ec_response_get_protocol_info *r = args->response;

	r->protocol_versions = (1 << 3);
	r->max_request_packet_size = RXBUF_MAX;
	r->max_response_packet_size = TXBUF_MAX;
	r->flags = 0;

	args->response_size = sizeof(*r);
	return EC_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO,
		     sps_get_protocol_info,
		     EC_VER_MASK(0));

static int command_sps(int argc, char **argv)
{
	if (argc > 1) {
		if (0 != strcasecmp(argv[1], "off"))
			return EC_ERROR_PARAM1;

		sps_hc_disable();
		ccprintf("SPS host commands disabled\n");
		return EC_SUCCESS;
	}

	sps_hc_enable();
	ccprintf("SPS host commands enabled\n");
	return EC_SUCCESS;
}

DECLARE_CONSOLE_COMMAND(spshc, command_sps,
			"[off]",
			"Enable (default) or disable SPS host commands",
			NULL);