summaryrefslogtreecommitdiff
path: root/board/cr50/usb_spi.c
blob: 66083de4b12202a2f8d5e3836010869f2d6e0a94 (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
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
/* Copyright 2016 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 "byteorder.h"
#include "ccd_config.h"
#include "cryptoc/sha256.h"
#include "console.h"
#include "dcrypto.h"
#include "extension.h"
#include "gpio.h"
#include "hooks.h"
#include "physical_presence.h"
#include "registers.h"
#include "spi.h"
#include "spi_flash.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "tpm_registers.h"
#include "tpm_vendor_cmds.h"
#include "usb_spi.h"

#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)

/* Don't hash more than this at once */
#define MAX_SPI_HASH_SIZE (4 * 1024 * 1024)

/*
 * Buffer size to use for reading and hashing.  This must be a multiple of the
 * SHA256 block size (64 bytes) and at least 4 less than the maximum SPI
 * transaction size for H1 (0x80 bytes).  So, 64.
 */
#define SPI_HASH_CHUNK_SIZE 64

/* Timeout for auto-disabling SPI hash device, in microseconds */
#define SPI_HASH_TIMEOUT_US (60 * SECOND)

/* Current device for SPI hashing */
static uint8_t spi_hash_device = USB_SPI_DISABLE;

/*
 * Do we need to use NPCX7 gang programming mode?
 *
 * If 0, then we hold the EC in reset the whole time we've acquired the SPI
 * bus, to keep the EC from accessing it.
 *
 * If 1, then:
 *
 *	When we acquire the EC SPI bus, we need to reset the EC, assert the
 *	gang programmer enable, then take the EC out of reset so its boot ROM
 *	can map the EC's internal SPI bus to the EC gang programmer pins.
 *
 *	When we relinquish the EC SPI bus, we need to reset the EC again while
 *	keeping gang programmer deasserted, then take the EC out of reset.  The
 *	EC will then boot normally.
 */
static uint8_t use_npcx_gang_mode;

/*
 * Device and gang mode selected by last spihash command, for use by
 * spi_hash_pp_done().
 */
static uint8_t new_device;
static uint8_t new_gang_mode;

static void spi_hash_inactive_timeout(void);
DECLARE_DEFERRED(spi_hash_inactive_timeout);

/*****************************************************************************/
/*
 * Mutex and variable for tracking whether the SPI bus is used by the USB
 * connection or hashing commands.
 *
 * Access these ONLY through set_spi_bus_user() and get_spi_bus_user(), to
 * ensure thread-safe access to the SPI bus.
 */
static struct mutex spi_bus_user_mutex;
static enum spi_bus_user_t {
	SPI_BUS_USER_NONE = 0,
	SPI_BUS_USER_USB,
	SPI_BUS_USER_HASH
} spi_bus_user = SPI_BUS_USER_NONE;

/**
 * Set who's using the SPI bus.
 *
 * This is thread-safe and will not block if someone owns the bus.  You can't
 * take the bus if someone else has it, and you can only free it if you hold
 * it.  It has no extra effect if you already own the bus.
 *
 * @param user		What bus user is asking?
 * @param want_bus	Do we want the bus (!=0) or no longer want it (==0)?
 *
 * @return EC_SUCCESS, or non-zero error code.
 */
static int set_spi_bus_user(enum spi_bus_user_t user, int want_bus)
{
	int rv = EC_SUCCESS;

	/*
	 * Serialize access to bus user variable, but don't mutex lock the
	 * entire bus because that would freeze USB or the console instead of
	 * just failing.
	 */
	mutex_lock(&spi_bus_user_mutex);

	if (want_bus) {
		/* Can only take the bus if it's free or we already own it */
		if (spi_bus_user == SPI_BUS_USER_NONE)
			spi_bus_user = user;
		else if (spi_bus_user != user)
			rv = EC_ERROR_BUSY;
	} else {
		/* Can only free the bus if it was ours */
		if (spi_bus_user == user)
			spi_bus_user = SPI_BUS_USER_NONE;
		else
			rv = EC_ERROR_BUSY;
	}

	mutex_unlock(&spi_bus_user_mutex);

	return rv;
}

/**
 * Get the current SPI bus user.
 */
static enum spi_bus_user_t get_spi_bus_user(void)
{
	return spi_bus_user;
}

/*****************************************************************************/
/* Methods to enable / disable the SPI bus and pin mux */

static void disable_ec_ap_spi(void)
{
	int was_ap_spi_en = gpio_get_level(GPIO_AP_FLASH_SELECT);

	/* Disable EC SPI access. */
	gpio_set_level(GPIO_EC_FLASH_SELECT, 0);

	/* Disable AP SPI access. */
	if (was_ap_spi_en) {
		/*
		 * The fact that AP SPI access was enabled means that the EC was
		 * held in reset.  Therefore, it needs to be released here.
		 */
		gpio_set_level(GPIO_AP_FLASH_SELECT, 0);
		deassert_ec_rst();
		deassert_sys_rst();
	}
}

static void enable_ec_spi(void)
{
	/* Select EC flash */
	gpio_set_level(GPIO_AP_FLASH_SELECT, 0);
	gpio_set_level(GPIO_EC_FLASH_SELECT, 1);

	/*
	 * Note that we don't hold the EC in reset here.  This is because some
	 * ECs with internal SPI flash cannot be held in reset in order to
	 * access the flash.
	 */
}

static void enable_ap_spi(void)
{
	/* Select AP flash */
	gpio_set_level(GPIO_AP_FLASH_SELECT, 1);
	gpio_set_level(GPIO_EC_FLASH_SELECT, 0);

	/*
	 * On some systems SYS_RST_L is not level sensitive, so the only way to
	 * be sure we're holding the AP in reset is to hold the EC in reset.
	 */
	assert_ec_rst();
}

/**
 * Enable the pin mux to the SPI master port.
 */
static void enable_spi_pinmux(void)
{
	GWRITE_FIELD(PINMUX, DIOA4_CTL, PD, 0);    /* SPI_MOSI */
	GWRITE_FIELD(PINMUX, DIOA8_CTL, PD, 0);    /* SPI_CLK */

	/* Connect DIO A4, A8, and A14 to the SPI peripheral */
	GWRITE(PINMUX, DIOA4_SEL, 0); /* SPI_MOSI */
	GWRITE(PINMUX, DIOA8_SEL, 0); /* SPI_CS_L */
	GWRITE(PINMUX, DIOA14_SEL, 0); /* SPI_CLK */
	/* Set SPI_CS to be an internal pull up */
	GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 1);

	CPRINTS("%s: %s", __func__,
		gpio_get_level(GPIO_AP_FLASH_SELECT) ? "AP" : "EC");

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);
}

/**
 * Disable the pin mux to the SPI master port.
 */
static void disable_spi_pinmux(void)
{
	spi_enable(CONFIG_SPI_FLASH_PORT, 0);

	/* Disconnect SPI peripheral to tri-state pads */
	/* Disable internal pull up */
	GWRITE_FIELD(PINMUX, DIOA14_CTL, PU, 0);
	/* TODO: Implement way to get the gpio */
	ASSERT(GREAD(PINMUX, GPIO0_GPIO7_SEL) == GC_PINMUX_DIOA4_SEL);
	ASSERT(GREAD(PINMUX, GPIO0_GPIO8_SEL) == GC_PINMUX_DIOA8_SEL);
	ASSERT(GREAD(PINMUX, GPIO0_GPIO9_SEL) == GC_PINMUX_DIOA14_SEL);

	GWRITE_FIELD(PINMUX, DIOA4_CTL, PD, 1);    /* SPI_MOSI */
	GWRITE_FIELD(PINMUX, DIOA8_CTL, PD, 1);    /* SPI_CLK */

	/* Set SPI MOSI, CLK, and CS_L as inputs */
	GWRITE(PINMUX, DIOA4_SEL, GC_PINMUX_GPIO0_GPIO7_SEL);
	GWRITE(PINMUX, DIOA8_SEL, GC_PINMUX_GPIO0_GPIO8_SEL);
	GWRITE(PINMUX, DIOA14_SEL, GC_PINMUX_GPIO0_GPIO9_SEL);
}

/*****************************************************************************/
/* USB SPI methods */

int usb_spi_board_enable(struct usb_spi_config const *config)
{
	int host = config->state->enabled_host;

	/* Make sure we're allowed to enable the requested device */
	if (host == USB_SPI_EC) {
		if (!ccd_is_cap_enabled(CCD_CAP_EC_FLASH)) {
			CPRINTS("%s: EC access denied", __func__);
			return EC_ERROR_ACCESS_DENIED;
		}
	} else if (host == USB_SPI_AP) {
		if (!ccd_is_cap_enabled(CCD_CAP_AP_FLASH)) {
			CPRINTS("%s: AP access denied", __func__);
			return EC_ERROR_ACCESS_DENIED;
		}
	} else {
		CPRINTS("%s: device %d not supported", __func__, host);
		return EC_ERROR_INVAL;
	}

	if (set_spi_bus_user(SPI_BUS_USER_USB, 1) != EC_SUCCESS) {
		CPRINTS("%s: bus in use", __func__);
		return EC_ERROR_BUSY;
	}

	disable_ec_ap_spi();

	/*
	 * Only need to check EC vs. AP, because other hosts were ruled out
	 * above.
	 */
	if (host == USB_SPI_EC)
		enable_ec_spi();
	else
		enable_ap_spi();

	enable_spi_pinmux();
	return EC_SUCCESS;
}

void usb_spi_board_disable(struct usb_spi_config const *config)
{
	CPRINTS("%s", __func__);

	/* Only disable the SPI bus if we own it */
	if (get_spi_bus_user() != SPI_BUS_USER_USB)
		return;

	disable_spi_pinmux();
	disable_ec_ap_spi();
	set_spi_bus_user(SPI_BUS_USER_USB, 0);
}

int usb_spi_interface(struct usb_spi_config const *config,
		      struct usb_setup_packet *req)
{
	if (req->bmRequestType != (USB_DIR_OUT |
				    USB_TYPE_VENDOR |
				    USB_RECIP_INTERFACE))
		return 1;

	if (req->wValue  != 0 ||
	    req->wIndex  != config->interface ||
	    req->wLength != 0)
		return 1;

	if (!config->state->enabled_device)
		return 1;

	switch (req->bRequest) {
	case USB_SPI_REQ_ENABLE_AP:
		config->state->enabled_host = USB_SPI_AP;
		break;
	case USB_SPI_REQ_ENABLE_EC:
		config->state->enabled_host = USB_SPI_EC;
		break;
	case USB_SPI_REQ_ENABLE:
		CPRINTS("%s: Must specify target", __func__);
		/* Fall through... */
	case USB_SPI_REQ_DISABLE:
		config->state->enabled_host = USB_SPI_DISABLE;
		break;

	default:
		return 1;
	}

	/*
	 * Our state has changed, call the deferred function to handle the
	 * state change.
	 */
	hook_call_deferred(config->deferred, 0);
	return 0;
}

/*****************************************************************************/
/* Hashing support */

/**
 * Returns the content of SPI flash
 *
 * @param buf_usr Buffer to write flash contents
 * @param offset Flash offset to start reading from
 * @param bytes Number of bytes to read.
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
int spi_read_chunk(uint8_t *buf_usr, unsigned int offset, unsigned int bytes)
{
	uint8_t cmd[4];

	if (bytes > SPI_HASH_CHUNK_SIZE)
		return EC_ERROR_INVAL;

	cmd[0] = SPI_FLASH_READ;
	cmd[1] = (offset >> 16) & 0xFF;
	cmd[2] = (offset >> 8) & 0xFF;
	cmd[3] = offset & 0xFF;

	return spi_transaction(SPI_FLASH_DEVICE, cmd, 4, buf_usr, bytes);
}

/**
 * Reset EC out of gang programming mode if needed.
 */
static void spi_hash_stop_ec_device(void)
{
	/* If device is not currently EC, nothing to do */
	if (spi_hash_device != USB_SPI_EC)
		return;

	if (use_npcx_gang_mode) {
		/*
		 * EC was in gang mode.  Pulse reset without asserting gang
		 * programmer enable, so that when we take the EC out of reset
		 * it will boot normally.
		 */
		assert_ec_rst();
		usleep(200);
		use_npcx_gang_mode = 0;
	}

	/*
	 * Release EC from reset (either from above, or because gang progamming
	 * mode was disabled so the EC was held in reset during SPI access).
	 */
	deassert_ec_rst();
}

/**
 * Disable SPI hashing mode.
 *
 * @return Vendor command return code.
 */
static enum vendor_cmd_rc spi_hash_disable(void)
{
	if (spi_hash_device == USB_SPI_DISABLE)
		return VENDOR_RC_SUCCESS;

	/* Can't disable SPI if we don't own it */
	if (get_spi_bus_user() != SPI_BUS_USER_HASH)
		return VENDOR_RC_NOT_ALLOWED;

	/* Disable the SPI bus and chip select */
	disable_spi_pinmux();
	disable_ec_ap_spi();

	/* Stop the EC device, if it was active */
	spi_hash_stop_ec_device();

	/* Release the bus */
	spi_hash_device = USB_SPI_DISABLE;
	new_device = USB_SPI_DISABLE;
	new_gang_mode = 0;
	set_spi_bus_user(SPI_BUS_USER_HASH, 0);

	/* Disable inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data, -1);

	CPRINTS("%s", __func__);
	return VENDOR_RC_SUCCESS;
}

/**
 * Deferred function to disable SPI hash mode on inactivity.
 */
static void spi_hash_inactive_timeout(void)
{
	spi_hash_disable();
}

/**
 * Callback to set up the new SPI device after physical presence check.
 */
static void spi_hash_pp_done(void)
{
	/* Acquire the bus */
	if (set_spi_bus_user(SPI_BUS_USER_HASH, 1)) {
		CPRINTS("%s: bus busy", __func__);
		return;
	}

	/* Clear previous enable if needed */
	if (spi_hash_device != USB_SPI_DISABLE)
		disable_ec_ap_spi();

	/* Set up new device */
	if (new_device == USB_SPI_AP) {
		/* Stop the EC device, if it was previously active */
		spi_hash_stop_ec_device();

		enable_ap_spi();
	} else {
		/* Force the EC into reset and enable EC SPI bus */
		assert_ec_rst();
		enable_ec_spi();

		/*
		 * If EC is headed into gang programmer mode, need to release
		 * EC from reset after acquiring the bus.  EC_FLASH_SELECT runs
		 * to the EC's GP_SEL_ODL signal, which is what enables gang
		 * programmer mode.
		 */
		if (new_gang_mode) {
			usleep(200);
			deassert_ec_rst();
			use_npcx_gang_mode = 1;
		}
	}

	enable_spi_pinmux();
	spi_hash_device = new_device;

	/* Start inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data,
			   SPI_HASH_TIMEOUT_US);

	CPRINTS("%s: %s", __func__,
		(spi_hash_device == USB_SPI_AP ? "AP" : "EC"));
}

/* Process vendor subcommand dealing with Physical presence polling. */
static enum vendor_cmd_rc spihash_pp_poll(void *buf,
					  size_t input_size,
					  size_t *response_size)
{
	char *buffer = buf;

	if (spi_hash_device != USB_SPI_DISABLE) {
		buffer[0] = CCD_PP_DONE;
	} else {
		switch (physical_presense_fsm_state()) {
		case PP_AWAITING_PRESS:
			buffer[0] = CCD_PP_AWAITING_PRESS;
			break;
		case PP_BETWEEN_PRESSES:
			buffer[0] = CCD_PP_BETWEEN_PRESSES;
			break;
		default:
			buffer[0] = CCD_PP_CLOSED;
			break;
		}
	}
	*response_size = 1;
	return VENDOR_RC_SUCCESS;
}

/**
 * Set the SPI hashing device.
 *
 * @param dev		Device (enum usb_spi)
 * @param gang_mode	If non-zero, EC uses gang mode
 *
 * @return Vendor command return code
 */
static enum vendor_cmd_rc spi_hash_set_device(int dev, int gang_mode,
					      uint8_t *response_buf,
					      size_t *response_size)
{
	*response_size = 0;

	if (dev == spi_hash_device)
		return VENDOR_RC_SUCCESS;

	/* Enabling requires permission */
	if (!(ccd_is_cap_enabled(CCD_CAP_FLASH_READ)))
		return VENDOR_RC_NOT_ALLOWED;

	new_device = dev;
	new_gang_mode = gang_mode;

	/* Handle enabling */
	if (spi_hash_device == USB_SPI_DISABLE &&
	    !(ccd_is_cap_enabled(CCD_CAP_AP_FLASH) &&
	      ccd_is_cap_enabled(CCD_CAP_EC_FLASH))) {
		/*
		 * We were disabled, and CCD does not grant permission
		 * to both flash chips.  So we need physical presence
		 * to take the SPI bus.  That prevents a malicious
		 * peripheral from using this to reset the device.
		 *
		 * Technically, we could track the chips separately,
		 * and only require physical presence the first time we
		 * check a chip which CCD doesn't grant access to.  But
		 * that's more bookkeeping, so for now the only way to
		 * skip physical presence is to have access to both.
		 */
		int rv = physical_detect_start(0, spi_hash_pp_done);

		if (rv == EC_SUCCESS)
			return VENDOR_RC_IN_PROGRESS;

		*response_size = 1;
		response_buf[0] = rv;

		return VENDOR_RC_INTERNAL_ERROR;
	}

	/*
	 * If we're still here, we already own the SPI bus, and are
	 * changing which chip we're looking at.  Update hash device
	 * directly; no new physical presence required.
	 */
	spi_hash_pp_done();
	return VENDOR_RC_SUCCESS;
}

static enum vendor_cmd_rc spi_hash_dump(uint8_t *dest, uint32_t offset,
					uint32_t size)
{
	/* Fail if we don't own the bus */
	if (get_spi_bus_user() != SPI_BUS_USER_HASH) {
		CPRINTS("%s: not enabled", __func__);
		return VENDOR_RC_NOT_ALLOWED;
	}

	/* Bump inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data,
			   SPI_HASH_TIMEOUT_US);

	if (size > SPI_HASH_MAX_RESPONSE_BYTES)
		return VENDOR_RC_BOGUS_ARGS;

	if (spi_read_chunk(dest, offset, size) != EC_SUCCESS) {
		CPRINTS("%s: read error at 0x%x", __func__, offset);
		return VENDOR_RC_READ_FLASH_FAIL;
	}

	return VENDOR_RC_SUCCESS;
}

static enum vendor_cmd_rc spi_hash_sha256(uint8_t *dest, uint32_t offset,
					  uint32_t size)
{
	HASH_CTX sha;
	uint8_t data[SPI_HASH_CHUNK_SIZE];
	int chunk_size = SPI_HASH_CHUNK_SIZE;
	int chunks = 0;

	/* Fail if we don't own the bus */
	if (get_spi_bus_user() != SPI_BUS_USER_HASH) {
		CPRINTS("%s: not enabled", __func__);
		return VENDOR_RC_NOT_ALLOWED;
	}

	/* Bump inactivity timer to turn hashing mode off */
	hook_call_deferred(&spi_hash_inactive_timeout_data,
			   SPI_HASH_TIMEOUT_US);

	if (size > MAX_SPI_HASH_SIZE)
		return VENDOR_RC_BOGUS_ARGS;

	CPRINTS("%s: 0x%x 0x%x", __func__, offset, size);

	DCRYPTO_SHA256_init(&sha, 0);

	for (chunks = 0; size > 0; chunks++) {
		int this_chunk = MIN(size, chunk_size);
		/* Read the data */
		if (spi_read_chunk(data, offset, this_chunk) != EC_SUCCESS) {
			CPRINTS("%s: read error at 0x%x", __func__, offset);
			return VENDOR_RC_READ_FLASH_FAIL;
		}

		/* Update hash */
		HASH_update(&sha, data, this_chunk);

		/* Give other things a chance to happen */
		if (!(chunks % 128))
			msleep(1);

		size -= this_chunk;
		offset += this_chunk;
	}

	memcpy(dest, HASH_final(&sha), SHA256_DIGEST_SIZE);

	CPRINTS("%s: done", __func__);
	return VENDOR_RC_SUCCESS;
}

/*
 * TPM Vendor command handler for SPI hash commands which need to be available
 * both through CLI and over /dev/tpm0.
 */
static enum vendor_cmd_rc spi_hash_vendor(enum vendor_cmd_cc code,
					  void *buf,
					  size_t input_size,
					  size_t *response_size)
{
	const struct vendor_cc_spi_hash_request *req = buf;
	enum vendor_cmd_rc rc;

	/* Default to no response data */
	*response_size = 0;

	/* Pick what to do based on subcommand. */
	switch (req->subcmd) {
	case SPI_HASH_SUBCMD_DISABLE:
		/* Handle disabling */
		return spi_hash_disable();
	case SPI_HASH_SUBCMD_AP:
		return spi_hash_set_device(USB_SPI_AP, 0, buf, response_size);
	case SPI_HASH_SUBCMD_EC:
		return spi_hash_set_device(USB_SPI_EC,
					   !!(req->flags &
					      SPI_HASH_FLAG_EC_GANG),
					   buf, response_size);
	case SPI_HASH_SUBCMD_SHA256:
		*response_size = SHA256_DIGEST_SIZE;
		rc = spi_hash_sha256(buf, req->offset, req->size);
		if (rc != VENDOR_RC_SUCCESS)
			*response_size = 0;
		return rc;
	case SPI_HASH_SUBCMD_DUMP:
		/* Save size before we overwrite it with data */
		*response_size = req->size;
		rc = spi_hash_dump(buf, req->offset, req->size);
		if (rc != VENDOR_RC_SUCCESS)
			*response_size = 0;
		return rc;
	case SPI_HASH_PP_POLL:
		return spihash_pp_poll(buf, input_size, response_size);

	default:
		CPRINTS("%s:%d - unknown subcommand %d",
			__func__, __LINE__, req->subcmd);
		*response_size = 0;
		return VENDOR_RC_NO_SUCH_SUBCOMMAND;
	}
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_SPI_HASH, spi_hash_vendor);

/**
 * Wrapper for hash commands which are passed through the TPM task context.
 */
static int hash_command_wrapper(int argc, char *argv[])
{
	int rv;
	struct vendor_cc_spi_hash_request req;
	struct tpm_cmd_header *tpm_header;
	const size_t command_size = sizeof(*tpm_header) +
			MAX(sizeof(req), SPI_HASH_MAX_RESPONSE_BYTES);
	uint8_t buf[command_size];
	uint8_t *p;
	uint32_t return_code;

	/* If no args, just return */
	if (argc < 2) {
		ccprintf("SPI hash device: %s\n",
			 (spi_hash_device ?
			  (spi_hash_device == USB_SPI_AP ? "AP" : "EC") :
			  "disable"));
		return EC_SUCCESS;
	}

	/* Parse args into stack-based struct */
	memset(&req, 0, sizeof(req));
	if (!strcasecmp(argv[1], "AP")) {
		req.subcmd = SPI_HASH_SUBCMD_AP;
	} else if (!strcasecmp(argv[1], "EC")) {
		req.subcmd = SPI_HASH_SUBCMD_EC;
		if (argc > 2 && !strcasecmp(argv[2], "gang"))
			req.flags |= SPI_HASH_FLAG_EC_GANG;
	} else if (!strcasecmp(argv[1], "disable")) {
		req.subcmd = SPI_HASH_SUBCMD_DISABLE;
	} else if (argc == 3) {
		req.subcmd = SPI_HASH_SUBCMD_SHA256;
		rv = parse_offset_size(argc, argv, 1, &req.offset, &req.size);
		if (rv)
			return rv;
	} else if (argc == 4 && !strcasecmp(argv[1], "dump")) {
		req.subcmd = SPI_HASH_SUBCMD_DUMP;
		rv = parse_offset_size(argc, argv, 2, &req.offset, &req.size);
		if (rv)
			return rv;
	} else {
		return EC_ERROR_PARAM1;
	}

	/* Build the extension command */
	tpm_header = (struct tpm_cmd_header *)buf;
	tpm_header->tag = htobe16(0x8001); /* TPM_ST_NO_SESSIONS */
	tpm_header->size = htobe32(command_size);
	tpm_header->command_code = htobe32(TPM_CC_VENDOR_BIT_MASK);
	tpm_header->subcommand_code = htobe16(VENDOR_CC_SPI_HASH);
	/* Copy request data */
	p = (uint8_t *)(tpm_header + 1);
	memcpy(p, &req, sizeof(req));

	tpm_alt_extension(tpm_header, command_size);

	/*
	 * Return status in the command code field now, in case of error,
	 * error code is the first byte after the header.
	 */
	return_code = be32toh(tpm_header->command_code);

	if ((return_code != EC_SUCCESS) &&
	    ((return_code - VENDOR_RC_ERR) != VENDOR_RC_IN_PROGRESS)) {
		rv = p[0];
	} else {
		rv = EC_SUCCESS;

		if (req.subcmd == SPI_HASH_SUBCMD_DUMP)
			ccprintf("data: %ph\n", HEX_BUF(p, req.size));
		else if (req.subcmd == SPI_HASH_SUBCMD_SHA256)
			ccprintf("hash: %ph\n", HEX_BUF(p, 32));
	}

	return rv;
}
DECLARE_SAFE_CONSOLE_COMMAND(spihash, hash_command_wrapper,
		     "ap | ec [gang] | disable | [dump] <offset> <size>",
		     "Hash SPI flash via TPM vendor command");