From patchwork Mon Apr 30 12:54:18 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "David R. Bild" X-Patchwork-Id: 10371747 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 096EA6032A for ; Mon, 30 Apr 2018 12:55:03 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id EA83B28997 for ; Mon, 30 Apr 2018 12:55:02 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id DEE982899E; Mon, 30 Apr 2018 12:55:02 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 411D7289A2 for ; Mon, 30 Apr 2018 12:55:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752521AbeD3MzA (ORCPT ); Mon, 30 Apr 2018 08:55:00 -0400 Received: from mail-io0-f194.google.com ([209.85.223.194]:34671 "EHLO mail-io0-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751672AbeD3My6 (ORCPT ); Mon, 30 Apr 2018 08:54:58 -0400 Received: by mail-io0-f194.google.com with SMTP id p124-v6so10040500iod.1 for ; Mon, 30 Apr 2018 05:54:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xaptum-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=4RWy2ci9zQqThaMyU/w8n6wxYwnzEtevu1UP5pJXnTs=; b=Z7MS3h3guCxV51lh0MGO1T5cny1FTFzXgDXfpT2VURLBVs10nVVLneLFaQwgzARp7c NPEnXhEkEMeROzAonUGgpN8d/6Vj5LOIAKdiEkslDv0r5YK1lkUHAVvpSr2xZWvT3JP0 unTO9jIGfRV03l+pnR/dIH2okpc98W4GLn9f3O0eHzVF+v0XRtjds2/k+md1kPg2SmDF dTJckcHNr8OkOERQE7L53X6ZZxhhVFWlvE1+MgZe2KZiksjelm43UJ/aFbKJ3ta7cG5A l+bjUzwjp0Cbv58Mg6UYLUbAVSa6ySZ2bJoZMMKRatJGLd23InK+YetTfQEdpv8pTBtY Sm5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=4RWy2ci9zQqThaMyU/w8n6wxYwnzEtevu1UP5pJXnTs=; b=fPZLa8eRI2p4PX5UrTDGNoZd7s5dzb939OaXOxnrpO3aKJGThf3rEPCo4lBiQZGPYP Egecx1LSFFXIPDnUmbLd9KV189QjVk3PpkcXXLM3k0Sjtk+h7VRnq60R2k/uNxtAsVTr wFJz9BQmYBWwNPOs1h/O+9d0ACuNhfgv5Qezdhg0fk/GtSgwmPs2A+/w8Kas025ttWx8 6TPoNkRKs29BBxGm9g2mfa+K1t3UIhq+qo+8tEMWprzgIDUxkfdw+LW7PTzcVt9E2q72 zg3tdMDFTYzDOdnL5C67XtKKNkwhsExUjo/68yLnyspzBoB9CfrAiv3rToVSY0Us6L2p Xg1Q== X-Gm-Message-State: ALQs6tAYvaCJyzSiF4I3YziL1g45cIR9SRyiKAH9KM5w9GlJus8BsDGe ts7fMvcAgNZn6Vu0+7dOzBgevHFB X-Google-Smtp-Source: AB8JxZoT66JCqvSseW/eVVlEmvs8FtnCODJSJP+EyJZcInm9+we+ZYyqfgVLVo7xmONTmkW75tiJpQ== X-Received: by 2002:a6b:109:: with SMTP id 9-v6mr11500694iob.138.1525092897474; Mon, 30 Apr 2018 05:54:57 -0700 (PDT) Received: from chimayo.davidbild.org ([136.30.8.158]) by smtp.gmail.com with ESMTPSA id d135-v6sm3427813ita.15.2018.04.30.05.54.55 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 30 Apr 2018 05:54:56 -0700 (PDT) From: "David R. Bild" To: Greg Kroah-Hartman Cc: Oliver Neukum , linux-usb@vger.kernel.org, "David R. Bild" Subject: [PATCH v2 2/2] usb: misc: xapea00x: perform platform initialization of TPM Date: Mon, 30 Apr 2018 07:54:18 -0500 Message-Id: <20180430125418.31344-3-david.bild@xaptum.com> X-Mailer: git-send-email 2.16.2 In-Reply-To: <20180430125418.31344-1-david.bild@xaptum.com> References: <20180430125418.31344-1-david.bild@xaptum.com> Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Normally the system platform (i.e., BIOS/UEFI for x86) is responsible for performing initialization of the TPM. For these modules, the host kernel is the platform, so we perform the initialization in the driver before registering the TPM with the kernel TPM subsystem. The initialization consists of issuing the TPM startup command, running the TPM self-test, and setting the TPM platform hierarchy authorization to a random, unsaved value so that it can never be used after the driver has loaded. Signed-off-by: David R. Bild --- drivers/usb/misc/xapea00x/Makefile | 3 +- drivers/usb/misc/xapea00x/xapea00x-core.c | 25 + drivers/usb/misc/xapea00x/xapea00x-tpm.c | 954 ++++++++++++++++++++++++++++++ 3 files changed, 981 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/misc/xapea00x/xapea00x-tpm.c diff --git a/drivers/usb/misc/xapea00x/Makefile b/drivers/usb/misc/xapea00x/Makefile index c4bcd7524c31..aa3f8803cdf5 100644 --- a/drivers/usb/misc/xapea00x/Makefile +++ b/drivers/usb/misc/xapea00x/Makefile @@ -4,4 +4,5 @@ # obj-$(CONFIG_USB_XAPEA00X) += xapea00x.o -xapea00x-y += xapea00x-core.o xapea00x-bridge.o +xapea00x-y += xapea00x-core.o xapea00x-bridge.o xapea00x-tpm.o + diff --git a/drivers/usb/misc/xapea00x/xapea00x-core.c b/drivers/usb/misc/xapea00x/xapea00x-core.c index 68caf3bf9be6..a1be7796295d 100644 --- a/drivers/usb/misc/xapea00x/xapea00x-core.c +++ b/drivers/usb/misc/xapea00x/xapea00x-core.c @@ -285,6 +285,31 @@ static void xapea00x_tpm_probe(struct work_struct *work) struct spi_device *tpm; int retval; + mutex_lock(&dev->usb_mutex); + if (!dev->interface) { + retval = -ENODEV; + goto out; + } + /* + * This driver is the "platform" in TPM terminology. Before + * passing control of the TPM to the Linux TPM subsystem, do + * the TPM initialization normally done by the platform code + * (e.g., BIOS). + */ + retval = xapea00x_tpm_platform_initialize(dev); + if (retval) { + dev_err(&dev->interface->dev, + "unable to do TPM platform initialization: %d\n", + retval); + goto err; + } + + /* + * Now register the TPM with the Linux TPM subsystem. This + * may call through to xapea00x_spi_transfer_one_message(), so + * don't hold usb_mutex here. + */ + mutex_unlock(&dev->usb_mutex); tpm = spi_new_device(spi_master, &tpm_board_info); mutex_lock(&dev->usb_mutex); if (!dev->interface) { diff --git a/drivers/usb/misc/xapea00x/xapea00x-tpm.c b/drivers/usb/misc/xapea00x/xapea00x-tpm.c new file mode 100644 index 000000000000..d0e5c5b6d75d --- /dev/null +++ b/drivers/usb/misc/xapea00x/xapea00x-tpm.c @@ -0,0 +1,954 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a + * TPM 2.0-based hardware module for authenticating IoT devices and + * gateways. + * + * Copyright (c) 2017 Xaptum, Inc. + */ + +#include "xapea00x.h" + +#define TPM_RETRY 50 +#define TPM_TIMEOUT 5 // msecs +#define TPM_TIMEOUT_RANGE_US 300 // usecs + +#define TIS_SHORT_TIMEOUT 750 // msecs +#define TIS_LONG_TIMEOUT 2000 // msecs + +#define TIS_MAX_BUF 1024 // byte +#define TIS_HEADER_LEN 10 // byte + +#define TPM2_TIMEOUT_A 750 // msecs +#define TPM2_TIMEOUT_B 2000 // msecs +#define TPM2_TIMEOUT_C 200 // msecs +#define TPM2_TIMEOUT_D 30 // msecs + +#define TPM_ACCESS_0 0x0000 +#define TPM_STS_0 0x0018 +#define TPM_DATA_FIFO_0 0x0024 + +#define TPM2_ST_NO_SESSIONS 0x8001 +#define TPM2_ST_SESSIONS 0x8002 + +#define TPM2_CC_STARTUP 0x0144 +#define TPM2_CC_SHUTDOWN 0x0145 +#define TPM2_CC_SELF_TEST 0x0143 +#define TPM2_CC_GET_RANDOM 0x017B +#define TPM2_CC_HIERARCHY_CHANGE_AUTH 0x0129 +#define TPM2_CC_DICT_ATTACK_LOCK_RST 0x0139 + +#define TPM_RC_SUCCESS 0x000 +#define TPM_RC_INITIALIZE 0x100 + +enum tis_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, + TPM_ACCESS_REQUEST_PENDING = 0x04, + TPM_ACCESS_REQUEST_USE = 0x02 +}; + +enum tis_status { + TPM_STS_VALID = 0x80, + TPM_STS_COMMAND_READY = 0x40, + TPM_STS_GO = 0x20, + TPM_STS_DATA_AVAIL = 0x10, + TPM_STS_DATA_EXPECT = 0x08, + TPM_STS_SELF_TEST_DONE = 0x04, + TPM_STS_RESPONSE_RETRY = 0x02 +}; + +struct tpm_tis_command { + __be16 tag; + __be32 size; + __be32 code; + u8 body[0]; +} __attribute__((__packed__)); + +/******************************************************************************* + * TPM TIS functions + */ + +/** + * xapea00x_tpm_msleep - sleep for at least the specified time. + * @msecs: minimum duration to sleep for in milliseconds + */ +static void xapea00x_tpm_msleep(int msecs) +{ + usleep_range(msecs * 1000, + msecs * 1000 + TPM_TIMEOUT_RANGE_US); +} + +/** + * xapea00x_tpm_transfer - execute an SPI transfer. + * @dev: pointer to the device + * @addr: the TPM TIS register address + * @in: if a write or write_read transfer, the data to write + * @out: if a read or write_read transfer, the buffer to read data into + * @len: the number of bytes to transfer + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_transfer(struct xapea00x_device *dev, + u32 addr, u8 *in, u8 *out, u16 len) +{ + u8 header[4]; + int i, retval; + + header[0] = (in ? 0x80 : 0x00) | (len - 1); + header[1] = 0xd4; + header[2] = addr >> 8; + header[3] = addr; + + retval = xapea00x_spi_transfer(dev, header, header, 4, 1, 0); + if (retval) + goto out; + + /* handle SPI wait states */ + if ((header[3] & 0x01) == 0x00) { + header[0] = 0; + + for (i = 0; i < TPM_RETRY; i++) { + retval = xapea00x_spi_transfer(dev, header, header, 1, + 1, 0); + if (retval) + goto out; + if ((header[0] & 0x01) == 00) + break; + } + + if (i == TPM_RETRY) { + retval = -ETIMEDOUT; + goto out; + } + } + + retval = xapea00x_spi_transfer(dev, out, in, len, 0, 0); + if (retval) + goto out; + +out: + return retval; +} + +/** + * xapea00x_tpm_read_bytes - read data from the TPM + * @dev: pointer to the device + * @addr: the register to read from + * @result: buffer to in which to place the read data + * @len: the number of bytes to read + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_read_bytes(struct xapea00x_device *dev, u32 addr, + void *result, u16 len) +{ + return xapea00x_tpm_transfer(dev, addr, result, NULL, len); +} + +/** + * xapea00x_tpm_write_bytes - write data from the TPM + * @dev: pointer to the device + * @addr: the register to write to + * @data: pointer to the data to write + * @len: the number of bytes to read + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_write_bytes(struct xapea00x_device *dev, u32 addr, + void *data, u16 len) +{ + return xapea00x_tpm_transfer(dev, addr, NULL, data, len); +} + +/** + * xapea00x_tpm_read8 - read one byte of data from the TPM + * @dev: pointer to the device + * @addr: the register to read from + * @result: pointer to the destination + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_read8(struct xapea00x_device *dev, u32 addr, u8 *result) +{ + return xapea00x_tpm_read_bytes(dev, addr, result, 1); +} + +/** + * xapea00x_tpm_write8 - write one byte of data to the TPM + * @dev: pointer to the device + * @addr: the register to write to + * @data: the byte to write + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_write8(struct xapea00x_device *dev, u32 addr, u8 data) +{ + return xapea00x_tpm_write_bytes(dev, addr, &data, 1); +} + +/** + * xapea00x_tpm_read32 - read one integer of data from the TPM + * @dev: pointer to the device + * @addr: the register to read from + * @result: pointer to the destination + * + * The method performs any required endianness conversion on the + * result. + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_read32(struct xapea00x_device *dev, u32 addr, + u32 *result) +{ + __le32 result_le; + int retval; + + retval = xapea00x_tpm_read_bytes(dev, addr, &result_le, + sizeof(result_le)); + if (retval) + goto out; + + *result = __le32_to_cpu(result_le); + retval = 0; + +out: + return retval; +} + +/** + * xapea00x_tpm_write32 - write one integer of data to the TPM + * @dev: pointer to the device + * @addr: the register to read from + * @data: the integer to write + * + * The method performs any required endianness conversion on the + * data. + * + * Context: !in_interrupt() + * + * Return: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_write32(struct xapea00x_device *dev, u32 addr, u32 data) +{ + __le32 data_le; + + data_le = __cpu_to_le32(data); + return xapea00x_tpm_write_bytes(dev, addr, &data_le, sizeof(data_le)); +} + +/** + * xapea00x_tpm_wait_reg8 - waits for the specified flags on the + * register to be set. + * @dev: pointer to the device + * @addr: the register to check + * @flags: mask of the flags to check + * @timeout_msecs: maximum amount of time to wait in milliseconds + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_wait_reg8(struct xapea00x_device *dev, + u8 addr, u8 flags, + int timeout_msecs) +{ + unsigned long stop = jiffies + msecs_to_jiffies(timeout_msecs); + u8 reg; + int retval; + + do { + retval = xapea00x_tpm_read8(dev, addr, ®); + if (retval) + goto out; + + if ((reg & flags) == flags) { + retval = 0; + goto out; + } + + xapea00x_tpm_msleep(TPM_TIMEOUT); + } while (time_before(jiffies, stop)); + + retval = -ETIMEDOUT; + +out: + return retval; +} + +/** + * xapea00x_tpm_request_locality0 - sets the active locality to 0 + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_request_locality0(struct xapea00x_device *dev) +{ + int retval; + + retval = xapea00x_tpm_write8(dev, TPM_ACCESS_0, TPM_ACCESS_REQUEST_USE); + if (retval) + goto out; + + retval = xapea00x_tpm_wait_reg8(dev, TPM_ACCESS_0, + TPM_ACCESS_ACTIVE_LOCALITY, + TPM2_TIMEOUT_A); + +out: + return retval; +} + +/** + * xapea00x_tpm_release_locality0 - release the active locality + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_release_locality0(struct xapea00x_device *dev) +{ + return xapea00x_tpm_write8(dev, TPM_ACCESS_0, + TPM_ACCESS_ACTIVE_LOCALITY); +} + +/** + * xapea00x_tpm_burst_count - fetch the number of bytes of data the + * TPM can currently handle in one burst. + * @dev: pointer to the device + * @counter: pointer to the destination for the count + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_burst_count(struct xapea00x_device *dev, u32 *count) +{ + u32 reg; + int retval; + + retval = xapea00x_tpm_read32(dev, TPM_STS_0, ®); + if (retval) + goto out; + + *count = (reg >> 8) & 0xFFFF; + retval = 0; + +out: + return retval; +} + +/** + * xapea00x_tpm_send - send the command to the TPM and execute it. + * @dev: pointer to the device + * @buf: the buffer containing the command + * @len: size of the buffer in bytes. + * + * N.B., the command may not fill the entire buffer. This function + * parses the command to determine its actual size. + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_send(struct xapea00x_device *dev, void *buf, u32 len) +{ + struct tpm_tis_command *cmd = buf; + u32 size, burst; + int retval; + + /* wait for TPM to be ready for command */ + retval = xapea00x_tpm_wait_reg8(dev, TPM_STS_0, TPM_STS_COMMAND_READY, + TPM2_TIMEOUT_B); + if (retval) + goto err; + + /* extract size of from header */ + size = __be32_to_cpu(cmd->size); + + if (size > len) { + retval = -EINVAL; + goto err; + } + + /* Write the command */ + while (size > 0) { + xapea00x_tpm_burst_count(dev, &burst); + burst = min(burst, size); + + retval = xapea00x_tpm_write_bytes(dev, TPM_DATA_FIFO_0, buf, + burst); + if (retval) + goto cancel; + + retval = xapea00x_tpm_wait_reg8(dev, TPM_STS_0, TPM_STS_VALID, + TPM2_TIMEOUT_C); + if (retval) + goto cancel; + + buf += burst; + size -= burst; + } + + /* Do it */ + retval = xapea00x_tpm_write8(dev, TPM_STS_0, TPM_STS_GO); + if (retval) + goto cancel; + + return 0; + +cancel: + /* Attempt to cancel */ + xapea00x_tpm_write8(dev, TPM_STS_0, TPM_STS_COMMAND_READY); + +err: + return retval; +} + +/** + * xapea00x_tpm_recv - recv a command response from the TPM. + * @dev: pointer to the device + * @buf: the buffer in which to store the response + * @len: size of the buffer in bytes. + * + * N.B., the result may not fill the entire buffer. The caller must + * parse the response header to determine its actual size. + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_recv(struct xapea00x_device *dev, void *buf, u32 len) +{ + struct tpm_tis_command *cmd = buf; + u32 burst; + u32 size; + int retval; + + /* wait for TPM to have data available */ + retval = xapea00x_tpm_wait_reg8(dev, TPM_STS_0, TPM_STS_DATA_AVAIL, + TPM2_TIMEOUT_C); + if (retval) + goto cancel; + + /* read the header */ + if (len < TIS_HEADER_LEN) { + retval = -EINVAL; + goto cancel; + } + + retval = xapea00x_tpm_read_bytes(dev, TPM_DATA_FIFO_0, buf, + TIS_HEADER_LEN); + if (retval) + goto cancel; + + /* extract size of body from header */ + size = __be32_to_cpu(cmd->size); + if (len < size) { + retval = -EINVAL; + goto cancel; + } + + size -= TIS_HEADER_LEN; + buf = &cmd->body; + + /* read the body */ + while (size > TIS_HEADER_LEN) { + xapea00x_tpm_burst_count(dev, &burst); + burst = min(burst, size); + + retval = xapea00x_tpm_read_bytes(dev, TPM_DATA_FIFO_0, buf, + burst); + if (retval) + goto cancel; + + size -= burst; + buf += burst; + } + + /* wait for valid */ + retval = xapea00x_tpm_wait_reg8(dev, TPM_STS_0, TPM_STS_VALID, + TPM2_TIMEOUT_C); + if (retval) + goto err; + + return 0; + +cancel: + xapea00x_tpm_write32(dev, TPM_STS_0, TPM_STS_COMMAND_READY); + +err: + return retval; +} + +/** + * xapea00x_tpm_transmit - transmit one command to the TPM and receive + * the response. + * @dev: pointer to the device + * @buf: the buffer containing the command and to place the response in. + * @len: size of the buffer in bytes. + * + * N.B., the command and result may not fill the entire buffer. The + * caller must parse the response header to determine its actual size. + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_transmit(struct xapea00x_device *dev, void *buf, + u32 len) +{ + int retval; + + retval = xapea00x_tpm_request_locality0(dev); + if (retval) + goto out; + + retval = xapea00x_tpm_send(dev, buf, len); + if (retval) + goto release; + + retval = xapea00x_tpm_wait_reg8(dev, TPM_STS_0, TPM_STS_DATA_AVAIL, + TIS_LONG_TIMEOUT); + if (retval) + goto cancel; + + retval = xapea00x_tpm_recv(dev, buf, len); + if (retval) + goto release; + + retval = 0; + goto release; + +cancel: + xapea00x_tpm_write32(dev, TPM_STS_0, TPM_STS_COMMAND_READY); + +release: + xapea00x_tpm_release_locality0(dev); + +out: + return retval; +} + +/** + * xapea00x_tpm_transmit_cmd - build and transmit one command to the + * TPM and receive the response. + * @dev: pointer to the device + * @tag: the TPM command header tag + * @cc: the TPM command header code + * @body: pointer to the command body + * @body_len: size in bytes of the command body + * @rc: pointer to the destination for the result code + * @result: pointer to the destination for the result body. If NULL, + * the result body will be discarded. + * @result_len: size in bytes of the result buffer + * @actual_len: size in bytes of the result body. May be NULL is + * result is NULL. + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_transmit_cmd(struct xapea00x_device *dev, + u16 tag, u32 cc, void *body, u32 body_len, + u32 *rc, void *result, u32 result_len, + u32 *actual_len) +{ + struct tpm_tis_command *cmd; + void *buf; + int buflen, cmdlen, retval; + + buflen = TIS_MAX_BUF + 4; + cmdlen = buflen - 2; /* reserve 2 bytes for realignment */ + + if (body_len + TIS_HEADER_LEN > cmdlen) { + retval = -E2BIG; + pr_notice("transmit_cmd: body_len + TIS_HEADER_LEN > cmdlen (%d)", + cmdlen); + goto out; + } + + buf = kzalloc(buflen, GFP_KERNEL); + if (!buf) { + retval = -ENOMEM; + goto out; + } + cmd = buf + 2; /* ensure all fields are properly aligned */ + + /* Build the command */ + cmd->tag = __cpu_to_be16(tag); + cmd->size = __cpu_to_be32(TIS_HEADER_LEN + body_len); + cmd->code = __cpu_to_be32(cc); + memcpy(&cmd->body, body, body_len); + + /* Execute the command */ + retval = xapea00x_tpm_transmit(dev, cmd, cmdlen); + if (retval) + goto free; + + /* Extract result code */ + *rc = __be32_to_cpu(cmd->code); + + /* Copy the response data */ + if (result) { + *actual_len = __be32_to_cpu(cmd->size) - TIS_HEADER_LEN; + if (*actual_len > result_len) { + retval = -E2BIG; + goto free; + } + memcpy(result, &cmd->body, *actual_len); + } + + retval = 0; + +free: + memset(buf, 0, buflen); + kzfree(buf); + +out: + return retval; +} + +/** + * xapea00x_tpm_transmit_cmd_simple - build and transmit one command to the + * TPM and discard the respone body. + * @dev: pointer to the device + * @tag: the TPM command header tag + * @cc: the TPM command header code + * @body: pointer to the command body + * @len: size in bytes of the command body + * @rc: pointer to the destination for the result code + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_transmit_cmd_simple(struct xapea00x_device *dev, + u16 tag, u32 cc, + void *body, u32 len, u32 *rc) +{ + return xapea00x_tpm_transmit_cmd(dev, tag, cc, body, len, rc, NULL, 0, + NULL); +} + +/******************************************************************************* + * TPM commands + */ + +/** + * xapea00x_tpm_startup - executes the TPM2_Startup command. + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_startup(struct xapea00x_device *dev) +{ + u8 body[2] = { 0x00, 0x00 }; + u32 rc; + int retval; + + retval = xapea00x_tpm_transmit_cmd_simple(dev, TPM2_ST_NO_SESSIONS, + TPM2_CC_STARTUP, body, + sizeof(body), &rc); + if (retval) + goto out; + + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE) { + retval = -EIO; + goto out; + } + + retval = 0; + +out: + return retval; +} + +/** + * xapea00x_tpm_self_test - executes the TPM2_SelfTest command. + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_self_test(struct xapea00x_device *dev) +{ + u8 body[1] = { 0x01 }; + u32 rc; + int retval; + + retval = xapea00x_tpm_transmit_cmd_simple(dev, TPM2_ST_NO_SESSIONS, + TPM2_CC_SELF_TEST, body, + sizeof(body), &rc); + if (retval) + goto out; + + if (rc != TPM_RC_SUCCESS) { + retval = -EIO; + goto out; + } + + retval = xapea00x_tpm_wait_reg8(dev, TPM_STS_0, TPM_STS_SELF_TEST_DONE, + TIS_LONG_TIMEOUT); + if (retval) { + retval = -EIO; + goto out; + } + +out: + return retval; +} + +/** + * xapea00x_tpm_dict_attack_lock_reset - executes the + * TPM2_DictionaryAttackLockReset command. + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int +xapea00x_tpm_dict_attack_lock_reset(struct xapea00x_device *dev) +{ + u8 body[17] = { 0x40, 0x00, 0x00, 0x0A, // TPM_RH_LOCKOUT + 0x00, 0x00, 0x00, 0x09, // authorizationSize + 0x40, 0x00, 0x00, 0x09, // TPM_RS_PW + 0x00, 0x00, // nonce size + // nonce + 0x01, // session attributes + 0x00, 0x00 // payload size + // payload + }; + u32 rc; + int retval; + + retval = xapea00x_tpm_transmit_cmd_simple(dev, + TPM2_ST_SESSIONS, + TPM2_CC_DICT_ATTACK_LOCK_RST, + body, sizeof(body), &rc); + if (retval) + goto out; + + if (rc != TPM_RC_SUCCESS) { + retval = -EIO; + goto out; + } + +out: + return retval; +} + +/** + * xapea00x_tpm_get_random - executes the TPM2_GetRandom command. + * @dev: pointer to the device + * @len: number of bytes to request + * @bytes: pointer to the destination + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_get_random(struct xapea00x_device *dev, u16 len, + void *bytes) +{ + __be16 body; + u8 *buf; + u32 buf_len, result_len; + u32 rc; + int retval; + + buf_len = len + 2; + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) { + retval = -ENOMEM; + goto out; + } + + while (len > 0) { + body = __cpu_to_be16(len); + + retval = xapea00x_tpm_transmit_cmd(dev, TPM2_ST_NO_SESSIONS, + TPM2_CC_GET_RANDOM, + &body, sizeof(body), + &rc, buf, buf_len, + &result_len); + + if (retval) + goto free; + + if (rc != TPM_RC_SUCCESS) { + retval = -EIO; + goto free; + } + + result_len = __be16_to_cpu(*(__be16 *)buf); + if (result_len > len) { + retval = -E2BIG; + goto free; + } + + memcpy(bytes, buf + 2, result_len); + len -= result_len; + } + + retval = 0; + +free: + memset(buf, 0, buf_len); + kzfree(buf); + +out: + return retval; +} + +/** + * xapea00x_tpm_randomize_platform_auth - sets the platform + * authorization to a random password and then discards it. + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +static int xapea00x_tpm_randomize_platform_auth(struct xapea00x_device *dev) +{ + u8 password[16]; + u8 body[35] = { 0x40, 0x00, 0x00, 0x0C, // TPM_RH_PLATFORM + 0x00, 0x00, 0x00, 0x09, // authorizationSize + 0x40, 0x00, 0x00, 0x09, // TPM_RS_PW + 0x00, 0x00, // nonce size + // nonce + 0x01, // session attributes + 0x00, 0x00, // old auth payload size + // old auth payload + 0x00, 0x10, // new auth payload size + 0x00, 0x00, 0x00, 0x00, // new auth payload + 0x00, 0x00, 0x00, 0x00, // new auth payload + 0x00, 0x00, 0x00, 0x00, // new auth payload + 0x00, 0x00, 0x00, 0x00 // new auth payload + }; + u32 rc; + int retval; + + retval = xapea00x_tpm_get_random(dev, sizeof(password), password); + if (retval) { + dev_err(&dev->interface->dev, + "TPM get random failed with %d\n", retval); + goto out; + } + + memcpy(body + 19, password, sizeof(password)); + + retval = xapea00x_tpm_transmit_cmd_simple(dev, TPM2_ST_SESSIONS, + TPM2_CC_HIERARCHY_CHANGE_AUTH, + &body, sizeof(body), &rc); + if (retval) + goto out; + + if (rc != TPM_RC_SUCCESS) { + retval = -EIO; + pr_notice("HierarchyChangeAuth result code: %d\n", rc); + goto out; + } + + retval = 0; + +out: + memset(password, 0, sizeof(password)); + memset(body, 0, sizeof(body)); + return retval; +} + +/** + * xapea00x_tpm_platform_initialize - performs the minimal + * initialization of the TPM normally performed by the platform code + * (e.g., BIOS). This consists of executing the TPM startup and + * self-test commands and setting the platform authorization password. + * + * @dev: pointer to the device + * + * Context: !in_interrupt() + * + * Result: If successful, 0. Otherwise a negative error code. + */ +int xapea00x_tpm_platform_initialize(struct xapea00x_device *dev) +{ + int retval; + + /* wait for TPM to be ready */ + retval = xapea00x_tpm_wait_reg8(dev, TPM_ACCESS_0, TPM_ACCESS_VALID, + TPM2_TIMEOUT_A); + if (retval) + goto out; + + /* issue TPM2_CC_STARTUP command */ + retval = xapea00x_tpm_startup(dev); + if (retval) { + dev_err(&dev->interface->dev, "TPM startup failed with %d\n", + retval); + goto out; + } + dev_info(&dev->interface->dev, "TPM startup complete\n"); + + /* issue TPM2_SELF_TEST command */ + retval = xapea00x_tpm_self_test(dev); + if (retval) { + dev_err(&dev->interface->dev, "TPM self-test failed with %d\n", + retval); + goto out; + } + dev_info(&dev->interface->dev, "TPM self-test complete\n"); + + /* + * The TPM will enter dictionary lockout mode if turned off + * too many times without a proper shutdown. For the + * "thumb-drive"-esque demo devices, this happens whenever it + * is unplugged. Dictionary attacks against the demo devices + * (XAP-EA-00{1,2}) don't matter, so reset the lockout on every + * boot. Production devices (XAP-EA-003) are internal mPCI-e + * devices that should not be hot-plugged, so do not need to be + * reset. + */ + if (dev->pid == USB_PRODUCT_ID_XAPEA001 || + dev->pid == USB_PRODUCT_ID_XAPEA002) { + retval = xapea00x_tpm_dict_attack_lock_reset(dev); + if (retval) { + dev_err(&dev->interface->dev, + "Resetting TPM lockout failed with %d\n", + retval); + goto out; + } + } + + /* set the platform authorization to random bytes */ + retval = xapea00x_tpm_randomize_platform_auth(dev); + if (retval) { + dev_err(&dev->interface->dev, + "Setting TPM platform auth failed with %d\n", + retval); + goto out; + } + + retval = 0; + +out: + return retval; +}