From patchwork Tue Jul 16 22:45:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 11046813 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8C63B14F6 for ; Tue, 16 Jul 2019 22:45:52 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 79F8A2000A for ; Tue, 16 Jul 2019 22:45:52 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6AD30283A6; Tue, 16 Jul 2019 22:45:52 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 28EA72000A for ; Tue, 16 Jul 2019 22:45:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388725AbfGPWpu (ORCPT ); Tue, 16 Jul 2019 18:45:50 -0400 Received: from mail-pl1-f196.google.com ([209.85.214.196]:42868 "EHLO mail-pl1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388877AbfGPWpV (ORCPT ); Tue, 16 Jul 2019 18:45:21 -0400 Received: by mail-pl1-f196.google.com with SMTP id ay6so10855689plb.9 for ; Tue, 16 Jul 2019 15:45:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=d7aVjiR+JK3ggYYZ7yXO08DXVJw3fb9HDv02tv0JnQs=; b=WsSLV5wxog6kvWIkL7tsl/dpBk3h6KCtQVzK3wRMQZycgoNetF0L2hlVtTIwv+mdb7 7cdOcd7E5s69ObFuTWG9V3q2o8BXcDlXi1AKT8WQLwj1muCboCZQwl1QXuY+k0KdETX/ dlhJO7ZtlW1HnyxfHdjlR711IWf195ERVsWbg= 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:mime-version:content-transfer-encoding; bh=d7aVjiR+JK3ggYYZ7yXO08DXVJw3fb9HDv02tv0JnQs=; b=BxDDEUtyyonOR5qMaex/rW146itIhTaUy3EUbGRkhYkNcr5jyL+U93GjXaqOAf4XR+ 2Eaco5QS28bHP3f2K0Fic7567C1L0gpL2tp7JGFyxaMS4gCBl3Y0n5dmm/cj1csLvz+3 V/DiZo7D1N1gsCf9lUIWpnmuGDndfy6AJByJQ3VqDTzJkeT22zlmjoiKzs+agPtzQOL6 8HjBGY1zklNFrLYDiNyEU+r7dUH3CTzOH+AccCnRsLMJy/v2oiGIHx+ryYiUHp5WwJdi i19MyQEkvrnmgFutVbpGPOPzYXyaW8WjadftEbzp1tRhilL5F528XeiAr1nWC43H2fb7 sVWQ== X-Gm-Message-State: APjAAAUw7L781DwJbaumJK/Y/3Qq/zsfXZT/ZWXkerM300ZEwpf1Rulk WfH2haDC1RAKM/hkO+R4o6CyLg== X-Google-Smtp-Source: APXvYqxPH9EK3keRCVn+uuCQzmztl+iHZMi6aDlVgkc8dTyHKxOKu67J/eMo+6OI5dwAp8p0Iblpbw== X-Received: by 2002:a17:902:6b86:: with SMTP id p6mr39696288plk.14.1563317121296; Tue, 16 Jul 2019 15:45:21 -0700 (PDT) Received: from smtp.gmail.com ([2620:15c:202:1:fa53:7765:582b:82b9]) by smtp.gmail.com with ESMTPSA id 64sm22182562pfe.128.2019.07.16.15.45.20 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 16 Jul 2019 15:45:20 -0700 (PDT) From: Stephen Boyd To: Peter Huewe , Jarkko Sakkinen Cc: linux-kernel@vger.kernel.org, Jason Gunthorpe , Arnd Bergmann , Greg Kroah-Hartman , linux-integrity@vger.kernel.org, Andrey Pronin , Duncan Laurie , Guenter Roeck , Herbert Xu Subject: [PATCH v2 1/6] hwrng: core: Freeze khwrng thread during suspend Date: Tue, 16 Jul 2019 15:45:13 -0700 Message-Id: <20190716224518.62556-2-swboyd@chromium.org> X-Mailer: git-send-email 2.22.0.510.g264f2c817a-goog In-Reply-To: <20190716224518.62556-1-swboyd@chromium.org> References: <20190716224518.62556-1-swboyd@chromium.org> MIME-Version: 1.0 Sender: linux-integrity-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The hwrng_fill() function can run while devices are suspending and resuming. If the hwrng is behind a bus such as i2c or SPI and that bus is suspended, the hwrng may hang the bus while attempting to add some randomness. It's been observed on ChromeOS devices with suspend-to-idle (s2idle) and an i2c based hwrng that this kthread may run and ask the hwrng device for randomness before the i2c bus has been resumed. Let's make this kthread freezable so that we don't try to touch the hwrng during suspend/resume. This ensures that we can't cause the hwrng backing driver to get into a bad state because the device is guaranteed to be resumed before the hwrng kthread is thawed. Cc: Andrey Pronin Cc: Herbert Xu Cc: Duncan Laurie Signed-off-by: Stephen Boyd --- drivers/char/hw_random/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c index 95be7228f327..3b88af3149a7 100644 --- a/drivers/char/hw_random/core.c +++ b/drivers/char/hw_random/core.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -421,7 +422,9 @@ static int hwrng_fillfn(void *unused) { long rc; - while (!kthread_should_stop()) { + set_freezable(); + + while (!kthread_freezable_should_stop(NULL)) { struct hwrng *rng; rng = get_current_rng(); From patchwork Tue Jul 16 22:45:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 11046811 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 62DAC14F6 for ; Tue, 16 Jul 2019 22:45:46 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 524202000A for ; Tue, 16 Jul 2019 22:45:46 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 46684283A6; Tue, 16 Jul 2019 22:45:46 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 DB5192000A for ; Tue, 16 Jul 2019 22:45:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389048AbfGPWpo (ORCPT ); Tue, 16 Jul 2019 18:45:44 -0400 Received: from mail-pf1-f195.google.com ([209.85.210.195]:41470 "EHLO mail-pf1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388920AbfGPWpW (ORCPT ); Tue, 16 Jul 2019 18:45:22 -0400 Received: by mail-pf1-f195.google.com with SMTP id m30so9812432pff.8 for ; Tue, 16 Jul 2019 15:45:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=XhoCZVaLlau9+Xc+5qSO9umR0QsUdpX7Z2QE44vnJZU=; b=bluu7D9B/mMWMLSKmrkYFqWlQqd81PwDK6IUpGDHR9A82/CADp8Ac7NeQrUBhy32gp un4BOzUZXGz/UosByptd3LvbToVwTST2LMbVWMk7+mDvzRsTkqdAMysmWFPl0zLwop74 VThHreHsZRuHz+/sHCR45nilICSBE7D5SjV2c= 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:mime-version:content-transfer-encoding; bh=XhoCZVaLlau9+Xc+5qSO9umR0QsUdpX7Z2QE44vnJZU=; b=Mn34zWyJwMsXJtbT4d2nS+gNdRowkEebtqlYAsxf8sh+ZFhbXSOwlzPdNFXLFTCILx ZROGPAkM6PNT7Bq7dZ7KgMrxycFYvBysNiP0m/5K6Q8bgUJ7LAJapnyqeVwwAJ2zWYkQ OLEVugk2sMsQd7bh4avgaNMnCthfiBXs3Ab2T7/ON8bafJkt1reDLGmw7ZTxL6F2N5dS G9MapkVfNR79NtENUZzcMU6BbExBFWJe28KnLh0lyq9OsREFgtPLrZuF8F/BJB2zAFWk wX6aR5MNsi49sb1hRzKCBhh+GVP4aMWGcaxpzWLyrJ1ZrGzyoYK/PQIVDKQaWlJZ2f33 fggQ== X-Gm-Message-State: APjAAAXv224tpPZLIj/Y2YUPwB7tYx5+CEREte9SrEblFy9nfBq52xqx RTPQWfDir5kNT49v0MDNoZFQWg== X-Google-Smtp-Source: APXvYqzJCyO68XJZcaLnDyrXCNdFh5UGj9LKILi3eXmr8hQVkr/gtkE5Z7MtsrtHorzGNfG0SBaK3Q== X-Received: by 2002:a17:90a:32ed:: with SMTP id l100mr39314318pjb.11.1563317122065; Tue, 16 Jul 2019 15:45:22 -0700 (PDT) Received: from smtp.gmail.com ([2620:15c:202:1:fa53:7765:582b:82b9]) by smtp.gmail.com with ESMTPSA id 64sm22182562pfe.128.2019.07.16.15.45.21 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 16 Jul 2019 15:45:21 -0700 (PDT) From: Stephen Boyd To: Peter Huewe , Jarkko Sakkinen Cc: Andrey Pronin , linux-kernel@vger.kernel.org, Jason Gunthorpe , Arnd Bergmann , Greg Kroah-Hartman , linux-integrity@vger.kernel.org, Duncan Laurie , Guenter Roeck , Dmitry Torokhov Subject: [PATCH v2 2/6] tpm_tis_core: add optional max xfer size check Date: Tue, 16 Jul 2019 15:45:14 -0700 Message-Id: <20190716224518.62556-3-swboyd@chromium.org> X-Mailer: git-send-email 2.22.0.510.g264f2c817a-goog In-Reply-To: <20190716224518.62556-1-swboyd@chromium.org> References: <20190716224518.62556-1-swboyd@chromium.org> MIME-Version: 1.0 Sender: linux-integrity-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Andrey Pronin If tpm reports a bigger burstcnt than allowed by the physical protocol, set burstcnt to the max allowed value. In practice, seen in case of xfer issues (e.g. in spi interface case, lost header causing flow control issues and wrong values returned on read from TPM_STS). Without catching, causes the physical layer to reject xfer. Signed-off-by: Andrey Pronin Reviewed-by: Dmitry Torokhov Reviewed-by: Jarkko Sakkinen [swboyd@chromium.org: Drop extra parenthesis in if statement] Signed-off-by: Stephen Boyd --- drivers/char/tpm/tpm_tis_core.c | 9 ++++++++- drivers/char/tpm/tpm_tis_core.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpm_tis_core.c b/drivers/char/tpm/tpm_tis_core.c index c3181ea9f271..5134b05487e5 100644 --- a/drivers/char/tpm/tpm_tis_core.c +++ b/drivers/char/tpm/tpm_tis_core.c @@ -268,8 +268,15 @@ static int get_burstcount(struct tpm_chip *chip) return rc; burstcnt = (value >> 8) & 0xFFFF; - if (burstcnt) + if (burstcnt) { + if (priv->phy_ops->max_xfer_size && + burstcnt > priv->phy_ops->max_xfer_size) { + dev_warn(&chip->dev, + "Bad burstcnt read: %d\n", burstcnt); + burstcnt = priv->phy_ops->max_xfer_size; + } return burstcnt; + } usleep_range(TPM_TIMEOUT_USECS_MIN, TPM_TIMEOUT_USECS_MAX); } while (time_before(jiffies, stop)); return -EBUSY; diff --git a/drivers/char/tpm/tpm_tis_core.h b/drivers/char/tpm/tpm_tis_core.h index 7337819f5d7b..248e8ac8fd02 100644 --- a/drivers/char/tpm/tpm_tis_core.h +++ b/drivers/char/tpm/tpm_tis_core.h @@ -106,6 +106,7 @@ struct tpm_tis_phy_ops { int (*read16)(struct tpm_tis_data *data, u32 addr, u16 *result); int (*read32)(struct tpm_tis_data *data, u32 addr, u32 *result); int (*write32)(struct tpm_tis_data *data, u32 addr, u32 src); + u16 max_xfer_size; }; static inline int tpm_tis_read_bytes(struct tpm_tis_data *data, u32 addr, From patchwork Tue Jul 16 22:45:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 11046809 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8C111912 for ; Tue, 16 Jul 2019 22:45:43 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7B4DD2000A for ; Tue, 16 Jul 2019 22:45:43 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6EC84283A6; Tue, 16 Jul 2019 22:45:43 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 1B6492000A for ; Tue, 16 Jul 2019 22:45:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389010AbfGPWpj (ORCPT ); Tue, 16 Jul 2019 18:45:39 -0400 Received: from mail-pf1-f195.google.com ([209.85.210.195]:42139 "EHLO mail-pf1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388943AbfGPWpX (ORCPT ); Tue, 16 Jul 2019 18:45:23 -0400 Received: by mail-pf1-f195.google.com with SMTP id q10so9800426pff.9 for ; Tue, 16 Jul 2019 15:45:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=72sm/MNTwsNcY36VTGuC/LZLkp7KY9tFB/Nl1CF7FuQ=; b=J94PBbim+Yv1GJt8vVgj6+wcSDxVIhoI24sx6Ohj38GaoLPK5smv0nkb3SYd2el77t vhonBa6mPzCYZBG5IGCaD7sBcIrd7SlCQO0LYmBNL8zSlXInCc6ICRsQN0Y4g8SQtwpS mYIMo0LaCbF6Ir8WNqnrThYgyhYz5bABz7R+g= 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:mime-version:content-transfer-encoding; bh=72sm/MNTwsNcY36VTGuC/LZLkp7KY9tFB/Nl1CF7FuQ=; b=ZeMKT4X5rMh/Y+l2gK2FsN2uCe8RO5E18v99Pc6KS5gKLm0uRmuCBjwUisfGR0t0HU EG/2QseRSFWIkZ1k18DckkHxXE8PXyWWY5+HQD8aW0RoTpppwzMA3mfBh2rjP5TOwDa1 FYRgwR+A6z6UlJJyAGTgQXNVm9BVzFZdrstcptql/YvA8Qk3V34hCPyRUZ3lOiowFb2I A0sGXS9r5qYazkOWdjCG7C3KZWBGQ7qMC0IqyVJxNTE23TGrncSnFIdZ00wrUgWu8om6 GFA6iQ8MtDZjmI5V8ekpzdFxMrdyX2hGoPtRd2oc/qJOSCQU6KNC5RXMzCEevLEgZlej /ONQ== X-Gm-Message-State: APjAAAXrRRsCi3AIWTV0ptrimRlE47+d9grABvg8xmjsT7YtB/30lQbP zjbS3vycrfVibeI+qxyejQYxVw== X-Google-Smtp-Source: APXvYqxPLoiQ/fQgpBzDfuDCkw0cAWxIoJ+jwZaN3mgwgHT7TpOVZgKqLx0rU89mFfmaN1nGc7j+/A== X-Received: by 2002:a63:2006:: with SMTP id g6mr36282313pgg.287.1563317122894; Tue, 16 Jul 2019 15:45:22 -0700 (PDT) Received: from smtp.gmail.com ([2620:15c:202:1:fa53:7765:582b:82b9]) by smtp.gmail.com with ESMTPSA id 64sm22182562pfe.128.2019.07.16.15.45.22 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 16 Jul 2019 15:45:22 -0700 (PDT) From: Stephen Boyd To: Peter Huewe , Jarkko Sakkinen Cc: Andrey Pronin , linux-kernel@vger.kernel.org, Jason Gunthorpe , Arnd Bergmann , Greg Kroah-Hartman , linux-integrity@vger.kernel.org, Duncan Laurie , Guenter Roeck , Dmitry Torokhov Subject: [PATCH v2 3/6] tpm_tis_spi: add max xfer size Date: Tue, 16 Jul 2019 15:45:15 -0700 Message-Id: <20190716224518.62556-4-swboyd@chromium.org> X-Mailer: git-send-email 2.22.0.510.g264f2c817a-goog In-Reply-To: <20190716224518.62556-1-swboyd@chromium.org> References: <20190716224518.62556-1-swboyd@chromium.org> MIME-Version: 1.0 Sender: linux-integrity-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Andrey Pronin Reject burstcounts larger than 64 bytes reported by tpm. SPI Hardware Protocol defined in section 6.4 of TCG PTP Spec supports up to 64 bytes of data in a transaction. Signed-off-by: Andrey Pronin Reviewed-by: Dmitry Torokhov Reviewed-by: Jarkko Sakkinen Signed-off-by: Stephen Boyd --- drivers/char/tpm/tpm_tis_spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/tpm/tpm_tis_spi.c b/drivers/char/tpm/tpm_tis_spi.c index 19513e622053..27393275a3f0 100644 --- a/drivers/char/tpm/tpm_tis_spi.c +++ b/drivers/char/tpm/tpm_tis_spi.c @@ -190,6 +190,7 @@ static const struct tpm_tis_phy_ops tpm_spi_phy_ops = { .read16 = tpm_tis_spi_read16, .read32 = tpm_tis_spi_read32, .write32 = tpm_tis_spi_write32, + .max_xfer_size = MAX_SPI_FRAMESIZE, }; static int tpm_tis_spi_probe(struct spi_device *dev) From patchwork Tue Jul 16 22:45:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 11046803 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 88A1214F6 for ; Tue, 16 Jul 2019 22:45:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7812428607 for ; Tue, 16 Jul 2019 22:45:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6C38F28635; Tue, 16 Jul 2019 22:45:26 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 1A4B028607 for ; Tue, 16 Jul 2019 22:45:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728746AbfGPWpZ (ORCPT ); Tue, 16 Jul 2019 18:45:25 -0400 Received: from mail-pf1-f194.google.com ([209.85.210.194]:36173 "EHLO mail-pf1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388965AbfGPWpY (ORCPT ); Tue, 16 Jul 2019 18:45:24 -0400 Received: by mail-pf1-f194.google.com with SMTP id r7so9815493pfl.3 for ; Tue, 16 Jul 2019 15:45:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=n5NY9hcxy/qmq+InJeq+MZhB52hgL5/XHKuoEKWCJRY=; b=QQhDmz+Am7zIZWfhfkXcmZA4KTBLGRUoGtoM6MU/qb/jbJajF+li7TvgwLh/nbUZNT 32HiuWT7lxFOy4PNA9GA48nPUnKmJFwf7Bo11YiNOfjWgG3s6g0vkINmqLZ2NxSzQgwW LpwQxIbjBSMF8oNuHBZJ04Clp3Ojer4C09NC0= 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:mime-version:content-transfer-encoding; bh=n5NY9hcxy/qmq+InJeq+MZhB52hgL5/XHKuoEKWCJRY=; b=he37yGm9uKKzuqtKxUQ24Le44XE4GHR6gcY7sQyI74Zl9ETiaoC+FWtAX2q0m/q6QM yHFtLB8bs59ALMX7lk0cqR2NzyJNWT/E4NlEXvSY4WdZBnTY7PFgwl0xltjZp6fgO09k DcBJxsbVzn2tragB6YMeU04Y4VBk8TAI0CYmt1t4sKPyjzLc8V+z0vHB8MbjgxdL16Br ayBGmADecaU6GheDa1Lr5eZQ24Tk1OWOk22tmzM37kY0aYRWNtc8MPvdo15NmbOPU961 UEORyfikNql80tiwYqaA2rfedmaEhCKu7HcmLu23Th/E+kTT4fiXtnYtcQoePcxSDxf3 HFNg== X-Gm-Message-State: APjAAAV6MRLpY3dZGyMl6VlgeG3X2bdBvcEPomd+SzMdYxNcz+UhCyy4 629xzN8E9rwEOrjF5M7LtuP6lw== X-Google-Smtp-Source: APXvYqy9H7LyEYLNDeFy27ZQi/NQGBvJDuaRVOmlwxY2kPGjQcHSYWDcwox3EfLdlVSqYkgPX7Bgnw== X-Received: by 2002:a17:90a:d58c:: with SMTP id v12mr38933628pju.7.1563317123728; Tue, 16 Jul 2019 15:45:23 -0700 (PDT) Received: from smtp.gmail.com ([2620:15c:202:1:fa53:7765:582b:82b9]) by smtp.gmail.com with ESMTPSA id 64sm22182562pfe.128.2019.07.16.15.45.23 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 16 Jul 2019 15:45:23 -0700 (PDT) From: Stephen Boyd To: Peter Huewe , Jarkko Sakkinen Cc: Andrey Pronin , linux-kernel@vger.kernel.org, Jason Gunthorpe , Arnd Bergmann , Greg Kroah-Hartman , linux-integrity@vger.kernel.org, Duncan Laurie , Guenter Roeck , Rob Herring Subject: [PATCH v2 4/6] dt-bindings: tpm: document properties for cr50 Date: Tue, 16 Jul 2019 15:45:16 -0700 Message-Id: <20190716224518.62556-5-swboyd@chromium.org> X-Mailer: git-send-email 2.22.0.510.g264f2c817a-goog In-Reply-To: <20190716224518.62556-1-swboyd@chromium.org> References: <20190716224518.62556-1-swboyd@chromium.org> MIME-Version: 1.0 Sender: linux-integrity-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Andrey Pronin Add TPM2.0 PTP FIFO compatible SPI interface for chips with Cr50 firmware. Signed-off-by: Andrey Pronin Reviewed-by: Rob Herring Signed-off-by: Stephen Boyd --- .../bindings/security/tpm/google,cr50.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Documentation/devicetree/bindings/security/tpm/google,cr50.txt diff --git a/Documentation/devicetree/bindings/security/tpm/google,cr50.txt b/Documentation/devicetree/bindings/security/tpm/google,cr50.txt new file mode 100644 index 000000000000..401f4ba281b7 --- /dev/null +++ b/Documentation/devicetree/bindings/security/tpm/google,cr50.txt @@ -0,0 +1,19 @@ +* H1 Secure Microcontroller with Cr50 Firmware on SPI Bus. + +H1 Secure Microcontroller running Cr50 firmware provides several +functions, including TPM-like functionality. It communicates over +SPI using the FIFO protocol described in the PTP Spec, section 6. + +Required properties: +- compatible: Should be "google,cr50". +- spi-max-frequency: Maximum SPI frequency. + +Example: + +&spi0 { + cr50@0 { + compatible = "google,cr50"; + reg = <0>; + spi-max-frequency = <800000>; + }; +}; From patchwork Tue Jul 16 22:45:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 11046807 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CBAB6912 for ; Tue, 16 Jul 2019 22:45:35 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B5EEF2000A for ; Tue, 16 Jul 2019 22:45:35 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A63D1283A6; Tue, 16 Jul 2019 22:45:35 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 6B54C2000A for ; Tue, 16 Jul 2019 22:45:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388977AbfGPWp1 (ORCPT ); Tue, 16 Jul 2019 18:45:27 -0400 Received: from mail-pf1-f196.google.com ([209.85.210.196]:36176 "EHLO mail-pf1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728681AbfGPWpZ (ORCPT ); Tue, 16 Jul 2019 18:45:25 -0400 Received: by mail-pf1-f196.google.com with SMTP id r7so9815513pfl.3 for ; Tue, 16 Jul 2019 15:45:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=vvzSMzmTMCIEHJw/xubCEDKMKHRySZgXglZWe5eSci8=; b=mykQV42+8Udjbe+TETOqbnJ27RgWTvFHoVCunbJoc7hs0QRuz6zpYH4LUmmZJ3x+82 oLkwMeqs/oefeT++tU9yX2rg5XbZiCNjkIGVjOek/7GXBFes31nR1hEDIzm9AGJjowaj Nuum0pVyN+8jI5FUZnNQmoguqdS0/lAg4DCss= 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:mime-version:content-transfer-encoding; bh=vvzSMzmTMCIEHJw/xubCEDKMKHRySZgXglZWe5eSci8=; b=ozlBcBoswUCZ5rkharbbZWtwIbALkDujUFY47+vQpTwHVaV6EWXVX63hdWDROK2B5j AZAaEICFT6LCY1G0o9+TjZ1OTcjBVfiJWC4c8CAeG8uiFrXDkbPbBBHxp+i5gcJJSyk2 a+W0lQy29ox8uh6B+E1gjqYlCFwVGqtKyNOt9wFoztkrP3XW4PGB6An51Xvuw0Gr0MKg 5HZHm/HXJdYIbqyRr3+FPBlvUUx2bRYDrsEmbgGRiN67gOENuE+oJLlT964pTkB4jBgs cRDsRB9WA7aYlsBFEz/aOitDUJplMyizNnFD1zV0CMWP0ebhVgZAlGmCBprXrfT6mdJR yRVw== X-Gm-Message-State: APjAAAXihqNVmZLLErTTY8fYIwSajh74K9pXbb+sZ9BTFP9WmX95z6Mc Nn0Z2RDpBWJOIiK8Zsa92s8PVQ== X-Google-Smtp-Source: APXvYqxI2kAJRMehCumRmj9NEANsQ31bZSzGYx7rdhjTLKrgIjZ05vD8Px4iJdzWqxma1EqxqtVsmA== X-Received: by 2002:a63:6c46:: with SMTP id h67mr27291823pgc.248.1563317124664; Tue, 16 Jul 2019 15:45:24 -0700 (PDT) Received: from smtp.gmail.com ([2620:15c:202:1:fa53:7765:582b:82b9]) by smtp.gmail.com with ESMTPSA id 64sm22182562pfe.128.2019.07.16.15.45.23 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 16 Jul 2019 15:45:24 -0700 (PDT) From: Stephen Boyd To: Peter Huewe , Jarkko Sakkinen Cc: Andrey Pronin , linux-kernel@vger.kernel.org, Jason Gunthorpe , Arnd Bergmann , Greg Kroah-Hartman , linux-integrity@vger.kernel.org, Duncan Laurie , Guenter Roeck Subject: [PATCH v2 5/6] tpm: add driver for cr50 on SPI Date: Tue, 16 Jul 2019 15:45:17 -0700 Message-Id: <20190716224518.62556-6-swboyd@chromium.org> X-Mailer: git-send-email 2.22.0.510.g264f2c817a-goog In-Reply-To: <20190716224518.62556-1-swboyd@chromium.org> References: <20190716224518.62556-1-swboyd@chromium.org> MIME-Version: 1.0 Sender: linux-integrity-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Andrey Pronin Add TPM2.0 PTP FIFO compatible SPI interface for chips with Cr50 firmware. The firmware running on the currently supported H1 Secure Microcontroller requires a special driver to handle its specifics: - need to ensure a certain delay between spi transactions, or else the chip may miss some part of the next transaction; - if there is no spi activity for some time, it may go to sleep, and needs to be waken up before sending further commands; - access to vendor-specific registers. Signed-off-by: Andrey Pronin [swboyd@chromium.org: Replace boilerplate with SPDX tag, drop suspended bit and remove ifdef checks in cr50.h, push tpm.h include into cr50.c] Signed-off-by: Stephen Boyd --- drivers/char/tpm/Kconfig | 16 ++ drivers/char/tpm/Makefile | 2 + drivers/char/tpm/cr50.c | 33 +++ drivers/char/tpm/cr50.h | 15 ++ drivers/char/tpm/cr50_spi.c | 450 ++++++++++++++++++++++++++++++++++++ 5 files changed, 516 insertions(+) create mode 100644 drivers/char/tpm/cr50.c create mode 100644 drivers/char/tpm/cr50.h create mode 100644 drivers/char/tpm/cr50_spi.c diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index 88a3c06fc153..b7028bfa6f87 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -114,6 +114,22 @@ config TCG_ATMEL will be accessible from within Linux. To compile this driver as a module, choose M here; the module will be called tpm_atmel. +config TCG_CR50 + bool + ---help--- + Common routines shared by drivers for Cr50-based devices. + +config TCG_CR50_SPI + tristate "Cr50 SPI Interface" + depends on SPI + select TCG_TIS_CORE + select TCG_CR50 + ---help--- + If you have a H1 secure module running Cr50 firmware on SPI bus, + say Yes and it will be accessible from within Linux. To compile + this driver as a module, choose M here; the module will be called + cr50_spi. + config TCG_INFINEON tristate "Infineon Technologies TPM Interface" depends on PNP diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile index a01c4cab902a..4e89538c73c8 100644 --- a/drivers/char/tpm/Makefile +++ b/drivers/char/tpm/Makefile @@ -27,6 +27,8 @@ obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o obj-$(CONFIG_TCG_TIS_I2C_NUVOTON) += tpm_i2c_nuvoton.o obj-$(CONFIG_TCG_NSC) += tpm_nsc.o obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o +obj-$(CONFIG_TCG_CR50) += cr50.o +obj-$(CONFIG_TCG_CR50_SPI) += cr50_spi.o obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o obj-$(CONFIG_TCG_IBMVTPM) += tpm_ibmvtpm.o obj-$(CONFIG_TCG_TIS_ST33ZP24) += st33zp24/ diff --git a/drivers/char/tpm/cr50.c b/drivers/char/tpm/cr50.c new file mode 100644 index 000000000000..8c83ec25f8e1 --- /dev/null +++ b/drivers/char/tpm/cr50.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2016 Google Inc. + */ + +/* + * This file contains common code for devices with Cr50 firmware. + */ + +#include +#include +#include "cr50.h" +#include "tpm.h" + +#ifdef CONFIG_PM_SLEEP +int cr50_resume(struct device *dev) +{ + if (pm_suspend_via_firmware()) + return tpm_pm_resume(dev); + + return 0; +} +EXPORT_SYMBOL(cr50_resume); + +int cr50_suspend(struct device *dev) +{ + if (pm_suspend_via_firmware()) + return tpm_pm_suspend(dev); + + return 0; +} +EXPORT_SYMBOL(cr50_suspend); +#endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/char/tpm/cr50.h b/drivers/char/tpm/cr50.h new file mode 100644 index 000000000000..1c635fae03e8 --- /dev/null +++ b/drivers/char/tpm/cr50.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2016 Google Inc. + * This file contains common code for devices with Cr50 firmware. + */ + +#ifndef __CR50_H__ +#define __CR50_H__ + +struct device; + +int cr50_resume(struct device *dev); +int cr50_suspend(struct device *dev); + +#endif /* __CR50_H__ */ diff --git a/drivers/char/tpm/cr50_spi.c b/drivers/char/tpm/cr50_spi.c new file mode 100644 index 000000000000..3c1b472297ad --- /dev/null +++ b/drivers/char/tpm/cr50_spi.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016 Google, Inc + * + * This device driver implements a TCG PTP FIFO interface over SPI for chips + * with Cr50 firmware. + * It is based on tpm_tis_spi driver by Peter Huewe and Christophe Ricard. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "cr50.h" +#include "tpm_tis_core.h" + +/* + * Cr50 timing constants: + * - can go to sleep not earlier than after CR50_SLEEP_DELAY_MSEC. + * - needs up to CR50_WAKE_START_DELAY_MSEC to wake after sleep. + * - requires waiting for "ready" IRQ, if supported; or waiting for at least + * CR50_NOIRQ_ACCESS_DELAY_MSEC between transactions, if IRQ is not supported. + * - waits for up to CR50_FLOW_CONTROL_MSEC for flow control 'ready' indication. + */ +#define CR50_SLEEP_DELAY_MSEC 1000 +#define CR50_WAKE_START_DELAY_MSEC 1 +#define CR50_NOIRQ_ACCESS_DELAY_MSEC 2 +#define CR50_READY_IRQ_TIMEOUT_MSEC TPM2_TIMEOUT_A +#define CR50_FLOW_CONTROL_MSEC TPM2_TIMEOUT_A +#define MAX_IRQ_CONFIRMATION_ATTEMPTS 3 + +#define MAX_SPI_FRAMESIZE 64 + +#define TPM_CR50_FW_VER(l) (0x0F90 | ((l) << 12)) +#define TPM_CR50_MAX_FW_VER_LEN 64 + +static unsigned short rng_quality = 1022; +module_param(rng_quality, ushort, 0644); +MODULE_PARM_DESC(rng_quality, + "Estimation of true entropy, in bits per 1024 bits."); + +struct cr50_spi_phy { + struct tpm_tis_data priv; + struct spi_device *spi_device; + + struct mutex time_track_mutex; + unsigned long last_access_jiffies; + unsigned long wake_after_jiffies; + + unsigned long access_delay_jiffies; + unsigned long sleep_delay_jiffies; + unsigned int wake_start_delay_msec; + + struct completion tpm_ready; + + unsigned int irq_confirmation_attempt; + bool irq_needs_confirmation; + bool irq_confirmed; + + u8 tx_buf[MAX_SPI_FRAMESIZE] ____cacheline_aligned; + u8 rx_buf[MAX_SPI_FRAMESIZE] ____cacheline_aligned; +}; + +static struct cr50_spi_phy *to_cr50_spi_phy(struct tpm_tis_data *data) +{ + return container_of(data, struct cr50_spi_phy, priv); +} + +/* + * The cr50 interrupt handler just signals waiting threads that the + * interrupt was asserted. It does not do any processing triggered + * by interrupts but is instead used to avoid fixed delays. + */ +static irqreturn_t cr50_spi_irq_handler(int dummy, void *dev_id) +{ + struct cr50_spi_phy *phy = dev_id; + + phy->irq_confirmed = true; + complete(&phy->tpm_ready); + + return IRQ_HANDLED; +} + +/* + * Cr50 needs to have at least some delay between consecutive + * transactions. Make sure we wait. + */ +static void cr50_ensure_access_delay(struct cr50_spi_phy *phy) +{ + /* + * Note: There is a small chance, if Cr50 is not accessed in a few days, + * that time_in_range will not provide the correct result after the wrap + * around for jiffies. In this case, we'll have an unneeded short delay, + * which is fine. + */ + unsigned long allowed_access = + phy->last_access_jiffies + phy->access_delay_jiffies; + unsigned long time_now = jiffies; + + if (time_in_range_open(time_now, + phy->last_access_jiffies, allowed_access)) { + unsigned long remaining = + wait_for_completion_timeout(&phy->tpm_ready, + allowed_access - time_now); + if (remaining == 0 && phy->irq_confirmed) { + dev_warn(&phy->spi_device->dev, + "Timeout waiting for TPM ready IRQ\n"); + } + } + if (phy->irq_needs_confirmation) { + if (phy->irq_confirmed) { + phy->irq_needs_confirmation = false; + phy->access_delay_jiffies = + msecs_to_jiffies(CR50_READY_IRQ_TIMEOUT_MSEC); + dev_info(&phy->spi_device->dev, + "TPM ready IRQ confirmed on attempt %u\n", + phy->irq_confirmation_attempt); + } else if (++phy->irq_confirmation_attempt > + MAX_IRQ_CONFIRMATION_ATTEMPTS) { + phy->irq_needs_confirmation = false; + dev_warn(&phy->spi_device->dev, + "IRQ not confirmed - will use delays\n"); + } + } +} + +/* + * Cr50 might go to sleep if there is no SPI activity for some time and + * miss the first few bits/bytes on the bus. In such case, wake it up + * by asserting CS and give it time to start up. + */ +static bool cr50_needs_waking(struct cr50_spi_phy *phy) +{ + /* + * Note: There is a small chance, if Cr50 is not accessed in a few days, + * that time_in_range will not provide the correct result after the wrap + * around for jiffies. In this case, we'll probably timeout or read + * incorrect value from TPM_STS and just retry the operation. + */ + return !time_in_range_open(jiffies, + phy->last_access_jiffies, + phy->wake_after_jiffies); +} + +static void cr50_wake_if_needed(struct cr50_spi_phy *phy) +{ + if (cr50_needs_waking(phy)) { + /* Assert CS, wait 1 msec, deassert CS */ + struct spi_transfer spi_cs_wake = { .delay_usecs = 1000 }; + + spi_sync_transfer(phy->spi_device, &spi_cs_wake, 1); + /* Wait for it to fully wake */ + msleep(phy->wake_start_delay_msec); + } + /* Reset the time when we need to wake Cr50 again */ + phy->wake_after_jiffies = jiffies + phy->sleep_delay_jiffies; +} + +/* + * Flow control: clock the bus and wait for cr50 to set LSB before + * sending/receiving data. TCG PTP spec allows it to happen during + * the last byte of header, but cr50 never does that in practice, + * and earlier versions had a bug when it was set too early, so don't + * check for it during header transfer. + */ +static int cr50_spi_flow_control(struct cr50_spi_phy *phy) +{ + unsigned long timeout_jiffies = + jiffies + msecs_to_jiffies(CR50_FLOW_CONTROL_MSEC); + struct spi_message m; + int ret; + struct spi_transfer spi_xfer = { + .rx_buf = phy->rx_buf, + .len = 1, + .cs_change = 1, + }; + + do { + spi_message_init(&m); + spi_message_add_tail(&spi_xfer, &m); + ret = spi_sync_locked(phy->spi_device, &m); + if (ret < 0) + return ret; + if (time_after(jiffies, timeout_jiffies)) { + dev_warn(&phy->spi_device->dev, + "Timeout during flow control\n"); + return -EBUSY; + } + } while (!(phy->rx_buf[0] & 0x01)); + return 0; +} + +static int cr50_spi_xfer_bytes(struct tpm_tis_data *data, u32 addr, + u16 len, const u8 *tx, u8 *rx) +{ + struct cr50_spi_phy *phy = to_cr50_spi_phy(data); + struct spi_message m; + struct spi_transfer spi_xfer = { + .tx_buf = phy->tx_buf, + .rx_buf = phy->rx_buf, + .len = 4, + .cs_change = 1, + }; + int ret; + + if (len > MAX_SPI_FRAMESIZE) + return -EINVAL; + + /* + * Do this outside of spi_bus_lock in case cr50 is not the + * only device on that spi bus. + */ + mutex_lock(&phy->time_track_mutex); + cr50_ensure_access_delay(phy); + cr50_wake_if_needed(phy); + + phy->tx_buf[0] = (tx ? 0x00 : 0x80) | (len - 1); + phy->tx_buf[1] = 0xD4; + phy->tx_buf[2] = (addr >> 8) & 0xFF; + phy->tx_buf[3] = addr & 0xFF; + + spi_message_init(&m); + spi_message_add_tail(&spi_xfer, &m); + + spi_bus_lock(phy->spi_device->master); + ret = spi_sync_locked(phy->spi_device, &m); + if (ret < 0) + goto exit; + + ret = cr50_spi_flow_control(phy); + if (ret < 0) + goto exit; + + spi_xfer.cs_change = 0; + spi_xfer.len = len; + if (tx) { + memcpy(phy->tx_buf, tx, len); + spi_xfer.rx_buf = NULL; + } else { + spi_xfer.tx_buf = NULL; + } + + spi_message_init(&m); + spi_message_add_tail(&spi_xfer, &m); + reinit_completion(&phy->tpm_ready); + ret = spi_sync_locked(phy->spi_device, &m); + if (rx) + memcpy(rx, phy->rx_buf, len); + +exit: + spi_bus_unlock(phy->spi_device->master); + phy->last_access_jiffies = jiffies; + mutex_unlock(&phy->time_track_mutex); + + return ret; +} + +static int cr50_spi_read_bytes(struct tpm_tis_data *data, u32 addr, + u16 len, u8 *result) +{ + return cr50_spi_xfer_bytes(data, addr, len, NULL, result); +} + +static int cr50_spi_write_bytes(struct tpm_tis_data *data, u32 addr, + u16 len, const u8 *value) +{ + return cr50_spi_xfer_bytes(data, addr, len, value, NULL); +} + +static int cr50_spi_read16(struct tpm_tis_data *data, u32 addr, u16 *result) +{ + int rc; + __le16 le_val; + + rc = data->phy_ops->read_bytes(data, addr, sizeof(u16), (u8 *)&le_val); + if (!rc) + *result = le16_to_cpu(le_val); + return rc; +} + +static int cr50_spi_read32(struct tpm_tis_data *data, u32 addr, u32 *result) +{ + int rc; + __le32 le_val; + + rc = data->phy_ops->read_bytes(data, addr, sizeof(u32), (u8 *)&le_val); + if (!rc) + *result = le32_to_cpu(le_val); + return rc; +} + +static int cr50_spi_write32(struct tpm_tis_data *data, u32 addr, u32 value) +{ + __le32 le_val = cpu_to_le32(value); + + return data->phy_ops->write_bytes(data, addr, sizeof(u32), + (u8 *)&le_val); +} + +static void cr50_get_fw_version(struct tpm_tis_data *data, char *fw_ver) +{ + int i, len = 0; + char fw_ver_block[4]; + + /* + * Write anything to TPM_CR50_FW_VER to start from the beginning + * of the version string + */ + tpm_tis_write8(data, TPM_CR50_FW_VER(data->locality), 0); + + /* Read the string, 4 bytes at a time, until we get '\0' */ + do { + tpm_tis_read_bytes(data, TPM_CR50_FW_VER(data->locality), 4, + fw_ver_block); + for (i = 0; i < 4 && fw_ver_block[i]; ++len, ++i) + fw_ver[len] = fw_ver_block[i]; + } while (i == 4 && len < TPM_CR50_MAX_FW_VER_LEN); + fw_ver[len] = 0; +} + +static const struct tpm_tis_phy_ops cr50_spi_phy_ops = { + .read_bytes = cr50_spi_read_bytes, + .write_bytes = cr50_spi_write_bytes, + .read16 = cr50_spi_read16, + .read32 = cr50_spi_read32, + .write32 = cr50_spi_write32, + .max_xfer_size = MAX_SPI_FRAMESIZE, +}; + +static int cr50_spi_probe(struct spi_device *dev) +{ + char fw_ver[TPM_CR50_MAX_FW_VER_LEN + 1]; + struct cr50_spi_phy *phy; + int rc; + + phy = devm_kzalloc(&dev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->spi_device = dev; + + phy->access_delay_jiffies = + msecs_to_jiffies(CR50_NOIRQ_ACCESS_DELAY_MSEC); + phy->sleep_delay_jiffies = msecs_to_jiffies(CR50_SLEEP_DELAY_MSEC); + phy->wake_start_delay_msec = CR50_WAKE_START_DELAY_MSEC; + + mutex_init(&phy->time_track_mutex); + phy->wake_after_jiffies = jiffies; + phy->last_access_jiffies = jiffies; + + init_completion(&phy->tpm_ready); + if (dev->irq > 0) { + rc = devm_request_irq(&dev->dev, dev->irq, cr50_spi_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "cr50_spi", phy); + if (rc < 0) { + if (rc == -EPROBE_DEFER) + return rc; + dev_warn(&dev->dev, "Requesting IRQ %d failed: %d\n", + dev->irq, rc); + /* + * This is not fatal, the driver will fall back to + * delays automatically, since tpm_ready will never + * be completed without a registered irq handler. + * So, just fall through. + */ + } else { + /* + * IRQ requested, let's verify that it is actually + * triggered, before relying on it. + */ + phy->irq_needs_confirmation = true; + } + } else { + dev_warn(&dev->dev, + "No IRQ - will use delays between transactions.\n"); + } + + phy->priv.rng_quality = rng_quality; + + rc = tpm_tis_core_init(&dev->dev, &phy->priv, -1, &cr50_spi_phy_ops, + NULL); + if (rc < 0) + return rc; + + cr50_get_fw_version(&phy->priv, fw_ver); + dev_info(&dev->dev, "Cr50 firmware version: %s\n", fw_ver); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cr50_spi_resume(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + struct tpm_tis_data *data = dev_get_drvdata(&chip->dev); + struct cr50_spi_phy *phy = to_cr50_spi_phy(data); + + /* + * Jiffies not increased during suspend, so we need to reset + * the time to wake Cr50 after resume. + */ + phy->wake_after_jiffies = jiffies; + + return cr50_resume(dev); +} +#endif + +static SIMPLE_DEV_PM_OPS(cr50_spi_pm, cr50_suspend, cr50_spi_resume); + +static int cr50_spi_remove(struct spi_device *dev) +{ + struct tpm_chip *chip = spi_get_drvdata(dev); + + tpm_chip_unregister(chip); + tpm_tis_remove(chip); + return 0; +} + +static const struct spi_device_id cr50_spi_id[] = { + { "cr50", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, cr50_spi_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_cr50_spi_match[] = { + { .compatible = "google,cr50", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_cr50_spi_match); +#endif + +static struct spi_driver cr50_spi_driver = { + .driver = { + .name = "cr50_spi", + .pm = &cr50_spi_pm, + .of_match_table = of_match_ptr(of_cr50_spi_match), + }, + .probe = cr50_spi_probe, + .remove = cr50_spi_remove, + .id_table = cr50_spi_id, +}; +module_spi_driver(cr50_spi_driver); + +MODULE_DESCRIPTION("Cr50 TCG PTP FIFO SPI driver"); +MODULE_LICENSE("GPL v2"); From patchwork Tue Jul 16 22:45:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 11046805 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DEFE814F6 for ; Tue, 16 Jul 2019 22:45:30 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CBD7128607 for ; Tue, 16 Jul 2019 22:45:30 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BF8F328635; Tue, 16 Jul 2019 22:45:30 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 60B0E28607 for ; Tue, 16 Jul 2019 22:45:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388988AbfGPWp2 (ORCPT ); Tue, 16 Jul 2019 18:45:28 -0400 Received: from mail-pg1-f193.google.com ([209.85.215.193]:42646 "EHLO mail-pg1-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388971AbfGPWp1 (ORCPT ); Tue, 16 Jul 2019 18:45:27 -0400 Received: by mail-pg1-f193.google.com with SMTP id t132so10134513pgb.9 for ; Tue, 16 Jul 2019 15:45:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=EVZsVk4VH+Fr9ud4/Nq2fxv+6JVS2EPT7DRwQ4/kQB0=; b=gEYL8oDZxbMzdz+8YF5tS8w1uIG4Phr5c87GDchl8vFvrNMnrai+N/Il1nzdQQoCWZ oAddOEEs6F/crksaErd4iPQPwJGh8mkB0iZJ24LCX3Sf74bwR11m45UPKxgAcTZDHNRT jkmaGTCDohKfB1Z8k3t4xXlACgTVQ4LsJ44yA= 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:mime-version:content-transfer-encoding; bh=EVZsVk4VH+Fr9ud4/Nq2fxv+6JVS2EPT7DRwQ4/kQB0=; b=RoD65ForDkGzpphnqyQ0pr5uAOpN3nuVqWZvJ+xGTLp54wlL+a21NRXmH7IzidUb01 0WVJawQnGp2FCWEGZey29qn5PvzjFw8ZRGRBu8wiYjicEDzZFqo7owO5qSNxIsgmPB4a lbcoM4Cv/D1Y0RZjOy2v8m4W1PISJXgCRuzxrIosT3NMSRojk0SVcUIwuDzs4K4Grw3/ 8jH9n7xce4QPV59WwUSOHn70aKijcn24mlbfDbeNSbuGFYxJVQ7sSNNsC+C2NpJdN1rt q53bmIjmMr7e5ZthJt9OAKGkqXY+PijTf+CGoweLpHNzMxbns0Mhl0JoW+YkXfQDhKoE tMqw== X-Gm-Message-State: APjAAAW6WXcxr6y2Y2QyJC3E+Rnl/hrKiO5QpDbFM3WXKciKjGJmqjjm hXJiztkravAS+lzM18bngLlreA== X-Google-Smtp-Source: APXvYqxGL/0Q5aIq43cfSXvzAaAr+PhuuxjqA95B13hfxzy/IAtt5fNjUErAnpKQM6RSZX4TuvxLCQ== X-Received: by 2002:a63:3009:: with SMTP id w9mr37718591pgw.260.1563317125908; Tue, 16 Jul 2019 15:45:25 -0700 (PDT) Received: from smtp.gmail.com ([2620:15c:202:1:fa53:7765:582b:82b9]) by smtp.gmail.com with ESMTPSA id 64sm22182562pfe.128.2019.07.16.15.45.24 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 16 Jul 2019 15:45:25 -0700 (PDT) From: Stephen Boyd To: Peter Huewe , Jarkko Sakkinen Cc: Duncan Laurie , linux-kernel@vger.kernel.org, Jason Gunthorpe , Arnd Bergmann , Greg Kroah-Hartman , linux-integrity@vger.kernel.org, Andrey Pronin , Guenter Roeck Subject: [PATCH v2 6/6] tpm: Add driver for cr50 on I2C Date: Tue, 16 Jul 2019 15:45:18 -0700 Message-Id: <20190716224518.62556-7-swboyd@chromium.org> X-Mailer: git-send-email 2.22.0.510.g264f2c817a-goog In-Reply-To: <20190716224518.62556-1-swboyd@chromium.org> References: <20190716224518.62556-1-swboyd@chromium.org> MIME-Version: 1.0 Sender: linux-integrity-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-integrity@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Duncan Laurie Add TPM 2.0 compatible I2C interface for chips with cr50 firmware. The firmware running on the currently supported H1 MCU requires a special driver to handle its specific protocol, and this makes it unsuitable to use tpm_tis_core_* and instead it must implement the underlying TPM protocol similar to the other I2C TPM drivers. - All 4 byes of status register must be read/written at once. - FIFO and burst count is limited to 63 and must be drained by AP. - Provides an interrupt to indicate when read response data is ready and when the TPM is finished processing write data. This driver is based on the existing infineon I2C TPM driver, which most closely matches the cr50 i2c protocol behavior. The driver is intentionally kept very similar in structure and style to the corresponding drivers in coreboot and depthcharge. Signed-off-by: Duncan Laurie [swboyd@chromium.org: Depend on i2c even if it's a module, replace boilier plate with SPDX tag, drop asm/byteorder.h include, simplify return from probe] Signed-off-by: Stephen Boyd --- drivers/char/tpm/Kconfig | 10 + drivers/char/tpm/Makefile | 1 + drivers/char/tpm/cr50_i2c.c | 705 ++++++++++++++++++++++++++++++++++++ 3 files changed, 716 insertions(+) create mode 100644 drivers/char/tpm/cr50_i2c.c diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index b7028bfa6f87..57a8c3540265 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -119,6 +119,16 @@ config TCG_CR50 ---help--- Common routines shared by drivers for Cr50-based devices. +config TCG_CR50_I2C + tristate "Cr50 I2C Interface" + depends on I2C + select TCG_CR50 + ---help--- + If you have a H1 secure module running Cr50 firmware on I2C bus, + say Yes and it will be accessible from within Linux. To compile + this driver as a module, choose M here; the module will be called + cr50_i2c. + config TCG_CR50_SPI tristate "Cr50 SPI Interface" depends on SPI diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile index 4e89538c73c8..3ac3448c21fa 100644 --- a/drivers/char/tpm/Makefile +++ b/drivers/char/tpm/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_TCG_NSC) += tpm_nsc.o obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o obj-$(CONFIG_TCG_CR50) += cr50.o obj-$(CONFIG_TCG_CR50_SPI) += cr50_spi.o +obj-$(CONFIG_TCG_CR50_I2C) += cr50_i2c.o obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o obj-$(CONFIG_TCG_IBMVTPM) += tpm_ibmvtpm.o obj-$(CONFIG_TCG_TIS_ST33ZP24) += st33zp24/ diff --git a/drivers/char/tpm/cr50_i2c.c b/drivers/char/tpm/cr50_i2c.c new file mode 100644 index 000000000000..25934c038b9b --- /dev/null +++ b/drivers/char/tpm/cr50_i2c.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2016 Google Inc. + * + * Based on Linux Kernel TPM driver by + * Peter Huewe + * Copyright (C) 2011 Infineon Technologies + */ + +/* + * cr50 is a firmware for H1 secure modules that requires special + * handling for the I2C interface. + * + * - Use an interrupt for transaction status instead of hardcoded delays + * - Must use write+wait+read read protocol + * - All 4 bytes of status register must be read/written at once + * - Burst count max is 63 bytes, and burst count behaves + * slightly differently than other I2C TPMs + * - When reading from FIFO the full burstcnt must be read + * instead of just reading header and determining the remainder + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cr50.h" +#include "tpm.h" + +#define CR50_MAX_BUFSIZE 63 +#define CR50_TIMEOUT_SHORT_MS 2 /* Short timeout during transactions */ +#define CR50_TIMEOUT_NOIRQ_MS 20 /* Timeout for TPM ready without IRQ */ +#define CR50_I2C_DID_VID 0x00281ae0L +#define CR50_I2C_MAX_RETRIES 3 /* Max retries due to I2C errors */ +#define CR50_I2C_RETRY_DELAY_LO 55 /* Min usecs between retries on I2C */ +#define CR50_I2C_RETRY_DELAY_HI 65 /* Max usecs between retries on I2C */ + +static unsigned short rng_quality = 1022; + +module_param(rng_quality, ushort, 0644); +MODULE_PARM_DESC(rng_quality, + "Estimation of true entropy, in bits per 1024 bits."); + +struct priv_data { + int irq; + int locality; + struct completion tpm_ready; + u8 buf[CR50_MAX_BUFSIZE + sizeof(u8)]; +}; + +/* + * The cr50 interrupt handler just signals waiting threads that the + * interrupt was asserted. It does not do any processing triggered + * by interrupts but is instead used to avoid fixed delays. + */ +static irqreturn_t cr50_i2c_int_handler(int dummy, void *dev_id) +{ + struct tpm_chip *chip = dev_id; + struct priv_data *priv = dev_get_drvdata(&chip->dev); + + complete(&priv->tpm_ready); + + return IRQ_HANDLED; +} + +/* + * Wait for completion interrupt if available, otherwise use a fixed + * delay for the TPM to be ready. + * + * Returns negative number for error, positive number for success. + */ +static int cr50_i2c_wait_tpm_ready(struct tpm_chip *chip) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + long rc; + + /* Use a safe fixed delay if interrupt is not supported */ + if (priv->irq <= 0) { + msleep(CR50_TIMEOUT_NOIRQ_MS); + return 1; + } + + /* Wait for interrupt to indicate TPM is ready to respond */ + rc = wait_for_completion_timeout(&priv->tpm_ready, + msecs_to_jiffies(chip->timeout_a)); + + if (rc == 0) + dev_warn(&chip->dev, "Timeout waiting for TPM ready\n"); + + return rc; +} + +static void cr50_i2c_enable_tpm_irq(struct tpm_chip *chip) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + + if (priv->irq > 0) { + reinit_completion(&priv->tpm_ready); + enable_irq(priv->irq); + } +} + +static void cr50_i2c_disable_tpm_irq(struct tpm_chip *chip) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + + if (priv->irq > 0) + disable_irq(priv->irq); +} + +/* + * cr50_i2c_transfer - transfer messages over i2c + * + * @adapter: i2c adapter + * @msgs: array of messages to transfer + * @num: number of messages in the array + * + * Call unlocked i2c transfer routine with the provided parameters and retry + * in case of bus errors. Returns the number of transferred messages. + */ +static int cr50_i2c_transfer(struct device *dev, struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + int rc, try; + + for (try = 0; try < CR50_I2C_MAX_RETRIES; try++) { + rc = __i2c_transfer(adapter, msgs, num); + if (rc > 0) + break; + if (try) + dev_warn(dev, "i2c transfer failed (attempt %d/%d): %d\n", + try+1, CR50_I2C_MAX_RETRIES, rc); + usleep_range(CR50_I2C_RETRY_DELAY_LO, CR50_I2C_RETRY_DELAY_HI); + } + + return rc; +} + +/* + * cr50_i2c_read() - read from TPM register + * + * @chip: TPM chip information + * @addr: register address to read from + * @buffer: provided by caller + * @len: number of bytes to read + * + * 1) send register address byte 'addr' to the TPM + * 2) wait for TPM to indicate it is ready + * 3) read 'len' bytes of TPM response into the provided 'buffer' + * + * Returns negative number for error, 0 for success. + */ +static int cr50_i2c_read(struct tpm_chip *chip, u8 addr, u8 *buffer, size_t len) +{ + struct i2c_client *client = to_i2c_client(chip->dev.parent); + struct i2c_msg msg1 = { + .addr = client->addr, + .len = 1, + .buf = &addr + }; + struct i2c_msg msg2 = { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buffer + }; + int rc; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + + /* Prepare for completion interrupt */ + cr50_i2c_enable_tpm_irq(chip); + + /* Send the register address byte to the TPM */ + rc = cr50_i2c_transfer(&chip->dev, client->adapter, &msg1, 1); + if (rc <= 0) + goto out; + + /* Wait for TPM to be ready with response data */ + rc = cr50_i2c_wait_tpm_ready(chip); + if (rc < 0) + goto out; + + /* Read response data from the TPM */ + rc = cr50_i2c_transfer(&chip->dev, client->adapter, &msg2, 1); + +out: + cr50_i2c_disable_tpm_irq(chip); + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + + if (rc < 0) + return rc; + if (rc == 0) + return -EIO; /* No i2c segments transferred */ + + return 0; +} + +/* + * cr50_i2c_write() - write to TPM register + * + * @chip: TPM chip information + * @addr: register address to write to + * @buffer: data to write + * @len: number of bytes to write + * + * 1) prepend the provided address to the provided data + * 2) send the address+data to the TPM + * 3) wait for TPM to indicate it is done writing + * + * Returns negative number for error, 0 for success. + */ +static int cr50_i2c_write(struct tpm_chip *chip, u8 addr, u8 *buffer, + size_t len) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + struct i2c_client *client = to_i2c_client(chip->dev.parent); + struct i2c_msg msg1 = { + .addr = client->addr, + .len = len + 1, + .buf = priv->buf + }; + int rc; + + if (len > CR50_MAX_BUFSIZE) + return -EINVAL; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + + /* Prepend the 'register address' to the buffer */ + priv->buf[0] = addr; + memcpy(priv->buf + 1, buffer, len); + + /* Prepare for completion interrupt */ + cr50_i2c_enable_tpm_irq(chip); + + /* Send write request buffer with address */ + rc = cr50_i2c_transfer(&chip->dev, client->adapter, &msg1, 1); + if (rc <= 0) + goto out; + + /* Wait for TPM to be ready, ignore timeout */ + cr50_i2c_wait_tpm_ready(chip); + +out: + cr50_i2c_disable_tpm_irq(chip); + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + + if (rc < 0) + return rc; + if (rc == 0) + return -EIO; /* No i2c segments transferred */ + + return 0; +} + +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, +}; + +enum tis_defaults { + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 2000, /* 2 sec */ +}; + +#define TPM_ACCESS(l) (0x0000 | ((l) << 4)) +#define TPM_STS(l) (0x0001 | ((l) << 4)) +#define TPM_DATA_FIFO(l) (0x0005 | ((l) << 4)) +#define TPM_DID_VID(l) (0x0006 | ((l) << 4)) + +static int check_locality(struct tpm_chip *chip, int loc) +{ + u8 mask = TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY; + u8 buf; + int rc; + + rc = cr50_i2c_read(chip, TPM_ACCESS(loc), &buf, 1); + if (rc < 0) + return rc; + + if ((buf & mask) == mask) + return loc; + + return -EIO; +} + +static void release_locality(struct tpm_chip *chip, int force) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + u8 mask = TPM_ACCESS_VALID | TPM_ACCESS_REQUEST_PENDING; + u8 addr = TPM_ACCESS(priv->locality); + u8 buf; + + if (cr50_i2c_read(chip, addr, &buf, 1) < 0) + return; + + if (force || (buf & mask) == mask) { + buf = TPM_ACCESS_ACTIVE_LOCALITY; + cr50_i2c_write(chip, addr, &buf, 1); + } + + priv->locality = 0; +} + +static int request_locality(struct tpm_chip *chip, int loc) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + u8 buf = TPM_ACCESS_REQUEST_USE; + unsigned long stop; + int rc; + + if (check_locality(chip, loc) == loc) + return loc; + + rc = cr50_i2c_write(chip, TPM_ACCESS(loc), &buf, 1); + if (rc < 0) + return rc; + + stop = jiffies + chip->timeout_a; + do { + if (check_locality(chip, loc) == loc) { + priv->locality = loc; + return loc; + } + msleep(CR50_TIMEOUT_SHORT_MS); + } while (time_before(jiffies, stop)); + + return -ETIMEDOUT; +} + +/* cr50 requires all 4 bytes of status register to be read */ +static u8 cr50_i2c_tis_status(struct tpm_chip *chip) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + u8 buf[4]; + + if (cr50_i2c_read(chip, TPM_STS(priv->locality), buf, sizeof(buf)) < 0) + return 0; + return buf[0]; +} + +/* cr50 requires all 4 bytes of status register to be written */ +static void cr50_i2c_tis_ready(struct tpm_chip *chip) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + u8 buf[4] = { TPM_STS_COMMAND_READY }; + + cr50_i2c_write(chip, TPM_STS(priv->locality), buf, sizeof(buf)); + msleep(CR50_TIMEOUT_SHORT_MS); +} + +/* + * cr50 uses bytes 3:2 of status register for burst count and + * all 4 bytes must be read + */ +static int cr50_i2c_wait_burststs(struct tpm_chip *chip, u8 mask, + size_t *burst, int *status) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + unsigned long stop; + u8 buf[4]; + + /* wait for burstcount */ + stop = jiffies + chip->timeout_b; + do { + if (cr50_i2c_read(chip, TPM_STS(priv->locality), + (u8 *)&buf, sizeof(buf)) < 0) { + msleep(CR50_TIMEOUT_SHORT_MS); + continue; + } + + *status = *buf; + *burst = le16_to_cpup((__le16 *)(buf + 1)); + + if ((*status & mask) == mask && + *burst > 0 && *burst <= CR50_MAX_BUFSIZE) + return 0; + + msleep(CR50_TIMEOUT_SHORT_MS); + } while (time_before(jiffies, stop)); + + dev_err(&chip->dev, "Timeout reading burst and status\n"); + return -ETIMEDOUT; +} + +static int cr50_i2c_tis_recv(struct tpm_chip *chip, u8 *buf, size_t buf_len) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + int status, rc; + size_t burstcnt, cur, len, expected; + u8 addr = TPM_DATA_FIFO(priv->locality); + u8 mask = TPM_STS_VALID | TPM_STS_DATA_AVAIL; + + if (buf_len < TPM_HEADER_SIZE) + return -EINVAL; + + rc = cr50_i2c_wait_burststs(chip, mask, &burstcnt, &status); + if (rc < 0) + goto out_err; + + if (burstcnt > buf_len || burstcnt < TPM_HEADER_SIZE) { + dev_err(&chip->dev, + "Unexpected burstcnt: %zu (max=%zu, min=%d)\n", + burstcnt, buf_len, TPM_HEADER_SIZE); + rc = -EIO; + goto out_err; + } + + /* Read first chunk of burstcnt bytes */ + rc = cr50_i2c_read(chip, addr, buf, burstcnt); + if (rc < 0) { + dev_err(&chip->dev, "Read of first chunk failed\n"); + goto out_err; + } + + /* Determine expected data in the return buffer */ + expected = be32_to_cpup((__be32 *)(buf + 2)); + if (expected > buf_len) { + dev_err(&chip->dev, "Too much data in FIFO\n"); + goto out_err; + } + + /* Now read the rest of the data */ + cur = burstcnt; + while (cur < expected) { + /* Read updated burst count and check status */ + rc = cr50_i2c_wait_burststs(chip, mask, &burstcnt, &status); + if (rc < 0) + goto out_err; + + len = min_t(size_t, burstcnt, expected - cur); + rc = cr50_i2c_read(chip, addr, buf + cur, len); + if (rc < 0) { + dev_err(&chip->dev, "Read failed\n"); + goto out_err; + } + + cur += len; + } + + /* Ensure TPM is done reading data */ + rc = cr50_i2c_wait_burststs(chip, TPM_STS_VALID, &burstcnt, &status); + if (rc < 0) + goto out_err; + if (status & TPM_STS_DATA_AVAIL) { + dev_err(&chip->dev, "Data still available\n"); + rc = -EIO; + goto out_err; + } + + release_locality(chip, 0); + return cur; + +out_err: + /* Abort current transaction if still pending */ + if (cr50_i2c_tis_status(chip) & TPM_STS_COMMAND_READY) + cr50_i2c_tis_ready(chip); + + release_locality(chip, 0); + return rc; +} + +static int cr50_i2c_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + struct priv_data *priv = dev_get_drvdata(&chip->dev); + int rc, status; + size_t burstcnt, limit, sent = 0; + u8 tpm_go[4] = { TPM_STS_GO }; + unsigned long stop; + + rc = request_locality(chip, 0); + if (rc < 0) + return rc; + + /* Wait until TPM is ready for a command */ + stop = jiffies + chip->timeout_b; + while (!(cr50_i2c_tis_status(chip) & TPM_STS_COMMAND_READY)) { + if (time_after(jiffies, stop)) { + rc = -ETIMEDOUT; + goto out_err; + } + + cr50_i2c_tis_ready(chip); + } + + while (len > 0) { + u8 mask = TPM_STS_VALID; + + /* Wait for data if this is not the first chunk */ + if (sent > 0) + mask |= TPM_STS_DATA_EXPECT; + + /* Read burst count and check status */ + rc = cr50_i2c_wait_burststs(chip, mask, &burstcnt, &status); + if (rc < 0) + goto out_err; + + /* + * Use burstcnt - 1 to account for the address byte + * that is inserted by cr50_i2c_write() + */ + limit = min_t(size_t, burstcnt - 1, len); + rc = cr50_i2c_write(chip, TPM_DATA_FIFO(priv->locality), + &buf[sent], limit); + if (rc < 0) { + dev_err(&chip->dev, "Write failed\n"); + goto out_err; + } + + sent += limit; + len -= limit; + } + + /* Ensure TPM is not expecting more data */ + rc = cr50_i2c_wait_burststs(chip, TPM_STS_VALID, &burstcnt, &status); + if (rc < 0) + goto out_err; + if (status & TPM_STS_DATA_EXPECT) { + dev_err(&chip->dev, "Data still expected\n"); + rc = -EIO; + goto out_err; + } + + /* Start the TPM command */ + rc = cr50_i2c_write(chip, TPM_STS(priv->locality), tpm_go, + sizeof(tpm_go)); + if (rc < 0) { + dev_err(&chip->dev, "Start command failed\n"); + goto out_err; + } + return 0; + +out_err: + /* Abort current transaction if still pending */ + if (cr50_i2c_tis_status(chip) & TPM_STS_COMMAND_READY) + cr50_i2c_tis_ready(chip); + + release_locality(chip, 0); + return rc; +} + +static bool cr50_i2c_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == TPM_STS_COMMAND_READY); +} + +static const struct tpm_class_ops cr50_i2c = { + .flags = TPM_OPS_AUTO_STARTUP, + .status = &cr50_i2c_tis_status, + .recv = &cr50_i2c_tis_recv, + .send = &cr50_i2c_tis_send, + .cancel = &cr50_i2c_tis_ready, + .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_canceled = &cr50_i2c_req_canceled, +}; + +static int cr50_i2c_init(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tpm_chip *chip; + struct priv_data *priv; + u8 buf[4]; + u32 vendor; + int rc; + + chip = tpmm_chip_alloc(dev, &cr50_i2c); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* cr50 is a TPM 2.0 chip */ + chip->flags |= TPM_CHIP_FLAG_TPM2; + + /* Default timeouts */ + chip->timeout_a = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + chip->timeout_b = msecs_to_jiffies(TIS_LONG_TIMEOUT); + chip->timeout_c = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + chip->timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + + dev_set_drvdata(&chip->dev, priv); + init_completion(&priv->tpm_ready); + + if (client->irq > 0) { + rc = devm_request_irq(dev, client->irq, cr50_i2c_int_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev->driver->name, chip); + if (rc < 0) { + dev_err(dev, "Failed to probe IRQ %d\n", client->irq); + return rc; + } + + disable_irq(client->irq); + priv->irq = client->irq; + } else { + dev_warn(dev, "No IRQ, will use %ums delay for TPM ready\n", + CR50_TIMEOUT_NOIRQ_MS); + } + + rc = request_locality(chip, 0); + if (rc < 0) { + dev_err(dev, "Could not request locality\n"); + return rc; + } + + /* Read four bytes from DID_VID register */ + rc = cr50_i2c_read(chip, TPM_DID_VID(0), buf, sizeof(buf)); + if (rc < 0) { + dev_err(dev, "Could not read vendor id\n"); + release_locality(chip, 1); + return rc; + } + + vendor = le32_to_cpup((__le32 *)buf); + if (vendor != CR50_I2C_DID_VID) { + dev_err(dev, "Vendor ID did not match! ID was %08x\n", vendor); + release_locality(chip, 1); + return -ENODEV; + } + + dev_info(dev, "cr50 TPM 2.0 (i2c 0x%02x irq %d id 0x%x)\n", + client->addr, client->irq, vendor >> 16); + + chip->hwrng.quality = rng_quality; + + return tpm_chip_register(chip); +} + +static const struct i2c_device_id cr50_i2c_table[] = { + {"cr50_i2c", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cr50_i2c_table); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cr50_i2c_acpi_id[] = { + { "GOOG0005", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, cr50_i2c_acpi_id); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id of_cr50_i2c_match[] = { + { .compatible = "google,cr50", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_cr50_i2c_match); +#endif + +static int cr50_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + return cr50_i2c_init(client); +} + +static int cr50_i2c_remove(struct i2c_client *client) +{ + struct tpm_chip *chip = i2c_get_clientdata(client); + + tpm_chip_unregister(chip); + release_locality(chip, 1); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(cr50_i2c_pm, cr50_suspend, cr50_resume); + +static struct i2c_driver cr50_i2c_driver = { + .id_table = cr50_i2c_table, + .probe = cr50_i2c_probe, + .remove = cr50_i2c_remove, + .driver = { + .name = "cr50_i2c", + .pm = &cr50_i2c_pm, + .acpi_match_table = ACPI_PTR(cr50_i2c_acpi_id), + .of_match_table = of_match_ptr(of_cr50_i2c_match), + }, +}; + +module_i2c_driver(cr50_i2c_driver); + +MODULE_DESCRIPTION("cr50 TPM I2C Driver"); +MODULE_LICENSE("GPL");