cregit-Linux how code gets into the kernel

Release 4.7 drivers/char/tpm/tpm_i2c_nuvoton.c

Directory: drivers/char/tpm
/******************************************************************************
 * Nuvoton TPM I2C Device Driver Interface for WPCT301/NPCT501,
 * based on the TCG TPM Interface Spec version 1.2.
 * Specifications at www.trustedcomputinggroup.org
 *
 * Copyright (C) 2011, Nuvoton Technology Corporation.
 *  Dan Morav <dan.morav@nuvoton.com>
 * Copyright (C) 2013, Obsidian Research Corp.
 *  Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/>.
 *
 * Nuvoton contact information: APC.Support@nuvoton.com
 *****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/i2c.h>
#include "tpm.h"

/* I2C interface offsets */

#define TPM_STS                0x00

#define TPM_BURST_COUNT        0x01

#define TPM_DATA_FIFO_W        0x20

#define TPM_DATA_FIFO_R        0x40

#define TPM_VID_DID_RID        0x60
/* TPM command header size */

#define TPM_HEADER_SIZE        10

#define TPM_RETRY      5
/*
 * I2C bus device maximum buffer size w/o counting I2C address or command
 * i.e. max size required for I2C write is 34 = addr, command, 32 bytes data
 */

#define TPM_I2C_MAX_BUF_SIZE           32

#define TPM_I2C_RETRY_COUNT            32

#define TPM_I2C_BUS_DELAY              1       
/* msec */

#define TPM_I2C_RETRY_DELAY_SHORT      2       
/* msec */

#define TPM_I2C_RETRY_DELAY_LONG       10      
/* msec */


#define I2C_DRIVER_NAME "tpm_i2c_nuvoton"


struct priv_data {
	
unsigned int intrs;
};


static s32 i2c_nuvoton_read_buf(struct i2c_client *client, u8 offset, u8 size, u8 *data) { s32 status; status = i2c_smbus_read_i2c_block_data(client, offset, size, data); dev_dbg(&client->dev, "%s(offset=%u size=%u data=%*ph) -> sts=%d\n", __func__, offset, size, (int)size, data, status); return status; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe65100.00%1100.00%
Total65100.00%1100.00%


static s32 i2c_nuvoton_write_buf(struct i2c_client *client, u8 offset, u8 size, u8 *data) { s32 status; status = i2c_smbus_write_i2c_block_data(client, offset, size, data); dev_dbg(&client->dev, "%s(offset=%u size=%u data=%*ph) -> sts=%d\n", __func__, offset, size, (int)size, data, status); return status; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe65100.00%1100.00%
Total65100.00%1100.00%

#define TPM_STS_VALID 0x80 #define TPM_STS_COMMAND_READY 0x40 #define TPM_STS_GO 0x20 #define TPM_STS_DATA_AVAIL 0x10 #define TPM_STS_EXPECT 0x08 #define TPM_STS_RESPONSE_RETRY 0x02 #define TPM_STS_ERR_VAL 0x07 /* bit2...bit0 reads always 0 */ #define TPM_I2C_SHORT_TIMEOUT 750 /* ms */ #define TPM_I2C_LONG_TIMEOUT 2000 /* 2 sec */ /* read TPM_STS register */
static u8 i2c_nuvoton_read_status(struct tpm_chip *chip) { struct i2c_client *client = to_i2c_client(chip->pdev); s32 status; u8 data; status = i2c_nuvoton_read_buf(client, TPM_STS, 1, &data); if (status <= 0) { dev_err(chip->pdev, "%s() error return %d\n", __func__, status); data = TPM_STS_ERR_VAL; } return data; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe6997.18%150.00%
jarkko sakkinenjarkko sakkinen22.82%150.00%
Total71100.00%2100.00%

/* write byte to TPM_STS register */
static s32 i2c_nuvoton_write_status(struct i2c_client *client, u8 data) { s32 status; int i; /* this causes the current command to be aborted */ for (i = 0, status = -1; i < TPM_I2C_RETRY_COUNT && status < 0; i++) { status = i2c_nuvoton_write_buf(client, TPM_STS, 1, &data); msleep(TPM_I2C_BUS_DELAY); } return status; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe67100.00%1100.00%
Total67100.00%1100.00%

/* write commandReady to TPM_STS register */
static void i2c_nuvoton_ready(struct tpm_chip *chip) { struct i2c_client *client = to_i2c_client(chip->pdev); s32 status; /* this causes the current command to be aborted */ status = i2c_nuvoton_write_status(client, TPM_STS_COMMAND_READY); if (status < 0) dev_err(chip->pdev, "%s() fail to write TPM_STS.commandReady\n", __func__); }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe5196.23%150.00%
jarkko sakkinenjarkko sakkinen23.77%150.00%
Total53100.00%2100.00%

/* read burstCount field from TPM_STS register * return -1 on fail to read */
static int i2c_nuvoton_get_burstcount(struct i2c_client *client, struct tpm_chip *chip) { unsigned long stop = jiffies + chip->vendor.timeout_d; s32 status; int burst_count = -1; u8 data; /* wait for burstcount to be non-zero */ do { /* in I2C burstCount is 1 byte */ status = i2c_nuvoton_read_buf(client, TPM_BURST_COUNT, 1, &data); if (status > 0 && data > 0) { burst_count = min_t(u8, TPM_I2C_MAX_BUF_SIZE, data); break; } msleep(TPM_I2C_BUS_DELAY); } while (time_before(jiffies, stop)); return burst_count; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe101100.00%1100.00%
Total101100.00%1100.00%

/* * WPCT301/NPCT501 SINT# supports only dataAvail * any call to this function which is not waiting for dataAvail will * set queue to NULL to avoid waiting for interrupt */
static bool i2c_nuvoton_check_status(struct tpm_chip *chip, u8 mask, u8 value) { u8 status = i2c_nuvoton_read_status(chip); return (status != TPM_STS_ERR_VAL) && ((status & mask) == value); }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe42100.00%1100.00%
Total42100.00%1100.00%


static int i2c_nuvoton_wait_for_stat(struct tpm_chip *chip, u8 mask, u8 value, u32 timeout, wait_queue_head_t *queue) { if (chip->vendor.irq && queue) { s32 rc; struct priv_data *priv = chip->vendor.priv; unsigned int cur_intrs = priv->intrs; enable_irq(chip->vendor.irq); rc = wait_event_interruptible_timeout(*queue, cur_intrs != priv->intrs, timeout); if (rc > 0) return 0; /* At this point we know that the SINT pin is asserted, so we * do not need to do i2c_nuvoton_check_status */ } else { unsigned long ten_msec, stop; bool status_valid; /* check current status */ status_valid = i2c_nuvoton_check_status(chip, mask, value); if (status_valid) return 0; /* use polling to wait for the event */ ten_msec = jiffies + msecs_to_jiffies(TPM_I2C_RETRY_DELAY_LONG); stop = jiffies + timeout; do { if (time_before(jiffies, ten_msec)) msleep(TPM_I2C_RETRY_DELAY_SHORT); else msleep(TPM_I2C_RETRY_DELAY_LONG); status_valid = i2c_nuvoton_check_status(chip, mask, value); if (status_valid) return 0; } while (time_before(jiffies, stop)); } dev_err(chip->pdev, "%s(%02x, %02x) -> timeout\n", __func__, mask, value); return -ETIMEDOUT; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe20999.52%150.00%
jarkko sakkinenjarkko sakkinen10.48%150.00%
Total210100.00%2100.00%

/* wait for dataAvail field to be set in the TPM_STS register */
static int i2c_nuvoton_wait_for_data_avail(struct tpm_chip *chip, u32 timeout, wait_queue_head_t *queue) { return i2c_nuvoton_wait_for_stat(chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID, TPM_STS_DATA_AVAIL | TPM_STS_VALID, timeout, queue); }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe36100.00%1100.00%
Total36100.00%1100.00%

/* Read @count bytes into @buf from TPM_RD_FIFO register */
static int i2c_nuvoton_recv_data(struct i2c_client *client, struct tpm_chip *chip, u8 *buf, size_t count) { s32 rc; int burst_count, bytes2read, size = 0; while (size < count && i2c_nuvoton_wait_for_data_avail(chip, chip->vendor.timeout_c, &chip->vendor.read_queue) == 0) { burst_count = i2c_nuvoton_get_burstcount(client, chip); if (burst_count < 0) { dev_err(chip->pdev, "%s() fail to read burstCount=%d\n", __func__, burst_count); return -EIO; } bytes2read = min_t(size_t, burst_count, count - size); rc = i2c_nuvoton_read_buf(client, TPM_DATA_FIFO_R, bytes2read, &buf[size]); if (rc < 0) { dev_err(chip->pdev, "%s() fail on i2c_nuvoton_read_buf()=%d\n", __func__, rc); return -EIO; } dev_dbg(chip->pdev, "%s(%d):", __func__, bytes2read); size += bytes2read; } return size; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe16998.26%150.00%
jarkko sakkinenjarkko sakkinen31.74%150.00%
Total172100.00%2100.00%

/* Read TPM command results */
static int i2c_nuvoton_recv(struct tpm_chip *chip, u8 *buf, size_t count) { struct device *dev = chip->pdev; struct i2c_client *client = to_i2c_client(dev); s32 rc; int expected, status, burst_count, retries, size = 0; if (count < TPM_HEADER_SIZE) { i2c_nuvoton_ready(chip); /* return to idle */ dev_err(dev, "%s() count < header size\n", __func__); return -EIO; } for (retries = 0; retries < TPM_RETRY; retries++) { if (retries > 0) { /* if this is not the first trial, set responseRetry */ i2c_nuvoton_write_status(client, TPM_STS_RESPONSE_RETRY); } /* * read first available (> 10 bytes), including: * tag, paramsize, and result */ status = i2c_nuvoton_wait_for_data_avail( chip, chip->vendor.timeout_c, &chip->vendor.read_queue); if (status != 0) { dev_err(dev, "%s() timeout on dataAvail\n", __func__); size = -ETIMEDOUT; continue; } burst_count = i2c_nuvoton_get_burstcount(client, chip); if (burst_count < 0) { dev_err(dev, "%s() fail to get burstCount\n", __func__); size = -EIO; continue; } size = i2c_nuvoton_recv_data(client, chip, buf, burst_count); if (size < TPM_HEADER_SIZE) { dev_err(dev, "%s() fail to read header\n", __func__); size = -EIO; continue; } /* * convert number of expected bytes field from big endian 32 bit * to machine native */ expected = be32_to_cpu(*(__be32 *) (buf + 2)); if (expected > count) { dev_err(dev, "%s() expected > count\n", __func__); size = -EIO; continue; } rc = i2c_nuvoton_recv_data(client, chip, &buf[size], expected - size); size += rc; if (rc < 0 || size < expected) { dev_err(dev, "%s() fail to read remainder of result\n", __func__); size = -EIO; continue; } if (i2c_nuvoton_wait_for_stat( chip, TPM_STS_VALID | TPM_STS_DATA_AVAIL, TPM_STS_VALID, chip->vendor.timeout_c, NULL)) { dev_err(dev, "%s() error left over data\n", __func__); size = -ETIMEDOUT; continue; } break; } i2c_nuvoton_ready(chip); dev_dbg(chip->pdev, "%s() -> %d\n", __func__, size); return size; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe37199.46%150.00%
jarkko sakkinenjarkko sakkinen20.54%150.00%
Total373100.00%2100.00%

/* * Send TPM command. * * If interrupts are used (signaled by an irq set in the vendor structure) * tpm.c can skip polling for the data to be available as the interrupt is * waited for here */
static int i2c_nuvoton_send(struct tpm_chip *chip, u8 *buf, size_t len) { struct device *dev = chip->pdev; struct i2c_client *client = to_i2c_client(dev); u32 ordinal; size_t count = 0; int burst_count, bytes2write, retries, rc = -EIO; for (retries = 0; retries < TPM_RETRY; retries++) { i2c_nuvoton_ready(chip); if (i2c_nuvoton_wait_for_stat(chip, TPM_STS_COMMAND_READY, TPM_STS_COMMAND_READY, chip->vendor.timeout_b, NULL)) { dev_err(dev, "%s() timeout on commandReady\n", __func__); rc = -EIO; continue; } rc = 0; while (count < len - 1) { burst_count = i2c_nuvoton_get_burstcount(client, chip); if (burst_count < 0) { dev_err(dev, "%s() fail get burstCount\n", __func__); rc = -EIO; break; } bytes2write = min_t(size_t, burst_count, len - 1 - count); rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W, bytes2write, &buf[count]); if (rc < 0) { dev_err(dev, "%s() fail i2cWriteBuf\n", __func__); break; } dev_dbg(dev, "%s(%d):", __func__, bytes2write); count += bytes2write; rc = i2c_nuvoton_wait_for_stat(chip, TPM_STS_VALID | TPM_STS_EXPECT, TPM_STS_VALID | TPM_STS_EXPECT, chip->vendor.timeout_c, NULL); if (rc < 0) { dev_err(dev, "%s() timeout on Expect\n", __func__); rc = -ETIMEDOUT; break; } } if (rc < 0) continue; /* write last byte */ rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W, 1, &buf[count]); if (rc < 0) { dev_err(dev, "%s() fail to write last byte\n", __func__); rc = -EIO; continue; } dev_dbg(dev, "%s(last): %02x", __func__, buf[count]); rc = i2c_nuvoton_wait_for_stat(chip, TPM_STS_VALID | TPM_STS_EXPECT, TPM_STS_VALID, chip->vendor.timeout_c, NULL); if (rc) { dev_err(dev, "%s() timeout on Expect to clear\n", __func__); rc = -ETIMEDOUT; continue; } break; } if (rc < 0) { /* retries == TPM_RETRY */ i2c_nuvoton_ready(chip); return rc; } /* execute the TPM command */ rc = i2c_nuvoton_write_status(client, TPM_STS_GO); if (rc < 0) { dev_err(dev, "%s() fail to write Go\n", __func__); i2c_nuvoton_ready(chip); return rc; } ordinal = be32_to_cpu(*((__be32 *) (buf + 6))); rc = i2c_nuvoton_wait_for_data_avail(chip, tpm_calc_ordinal_duration(chip, ordinal), &chip->vendor.read_queue); if (rc) { dev_err(dev, "%s() timeout command duration\n", __func__); i2c_nuvoton_ready(chip); return rc; } dev_dbg(dev, "%s() -> %zd\n", __func__, len); return len; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe50299.80%150.00%
jarkko sakkinenjarkko sakkinen10.20%150.00%
Total503100.00%2100.00%


static bool i2c_nuvoton_req_canceled(struct tpm_chip *chip, u8 status) { return (status == TPM_STS_COMMAND_READY); }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe21100.00%1100.00%
Total21100.00%1100.00%

static const struct tpm_class_ops tpm_i2c = { .status = i2c_nuvoton_read_status, .recv = i2c_nuvoton_recv, .send = i2c_nuvoton_send, .cancel = i2c_nuvoton_ready, .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, .req_canceled = i2c_nuvoton_req_canceled, }; /* The only purpose for the handler is to signal to any waiting threads that * the interrupt is currently being asserted. The driver does not do any * processing triggered by interrupts, and the chip provides no way to mask at * the source (plus that would be slow over I2C). Run the IRQ as a one-shot, * this means it cannot be shared. */
static irqreturn_t i2c_nuvoton_int_handler(int dummy, void *dev_id) { struct tpm_chip *chip = dev_id; struct priv_data *priv = chip->vendor.priv; priv->intrs++; wake_up(&chip->vendor.read_queue); disable_irq_nosync(chip->vendor.irq); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe58100.00%1100.00%
Total58100.00%1100.00%


static int get_vid(struct i2c_client *client, u32 *res) { static const u8 vid_did_rid_value[] = { 0x50, 0x10, 0xfe }; u32 temp; s32 rc; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; rc = i2c_nuvoton_read_buf(client, TPM_VID_DID_RID, 4, (u8 *)&temp); if (rc < 0) return rc; /* check WPCT301 values - ignore RID */ if (memcmp(&temp, vid_did_rid_value, sizeof(vid_did_rid_value))) { /* * f/w rev 2.81 has an issue where the VID_DID_RID is not * reporting the right value. so give it another chance at * offset 0x20 (FIFO_W). */ rc = i2c_nuvoton_read_buf(client, TPM_DATA_FIFO_W, 4, (u8 *) (&temp)); if (rc < 0) return rc; /* check WPCT301 values - ignore RID */ if (memcmp(&temp, vid_did_rid_value, sizeof(vid_did_rid_value))) return -ENODEV; } *res = temp; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe154100.00%1100.00%
Total154100.00%1100.00%


static int i2c_nuvoton_probe(struct i2c_client *client, const struct i2c_device_id *id) { int rc; struct tpm_chip *chip; struct device *dev = &client->dev; u32 vid = 0; rc = get_vid(client, &vid); if (rc) return rc; dev_info(dev, "VID: %04X DID: %02X RID: %02X\n", (u16) vid, (u8) (vid >> 16), (u8) (vid >> 24)); chip = tpmm_chip_alloc(dev, &tpm_i2c); if (IS_ERR(chip)) return PTR_ERR(chip); chip->vendor.priv = devm_kzalloc(dev, sizeof(struct priv_data), GFP_KERNEL); if (!chip->vendor.priv) return -ENOMEM; init_waitqueue_head(&chip->vendor.read_queue); init_waitqueue_head(&chip->vendor.int_queue); /* Default timeouts */ chip->vendor.timeout_a = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); chip->vendor.timeout_b = msecs_to_jiffies(TPM_I2C_LONG_TIMEOUT); chip->vendor.timeout_c = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); chip->vendor.timeout_d = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); /* * I2C intfcaps (interrupt capabilitieis) in the chip are hard coded to: * TPM_INTF_INT_LEVEL_LOW | TPM_INTF_DATA_AVAIL_INT * The IRQ should be set in the i2c_board_info (which is done * automatically in of_i2c_register_devices, for device tree users */ chip->vendor.irq = client->irq; if (chip->vendor.irq) { dev_dbg(dev, "%s() chip-vendor.irq\n", __func__); rc = devm_request_irq(dev, chip->vendor.irq, i2c_nuvoton_int_handler, IRQF_TRIGGER_LOW, chip->devname, chip); if (rc) { dev_err(dev, "%s() Unable to request irq: %d for use\n", __func__, chip->vendor.irq); chip->vendor.irq = 0; } else { /* Clear any pending interrupt */ i2c_nuvoton_ready(chip); /* - wait for TPM_STS==0xA0 (stsValid, commandReady) */ rc = i2c_nuvoton_wait_for_stat(chip, TPM_STS_COMMAND_READY, TPM_STS_COMMAND_READY, chip->vendor.timeout_b, NULL); if (rc == 0) { /* * TIS is in ready state * write dummy byte to enter reception state * TPM_DATA_FIFO_W <- rc (0) */ rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W, 1, (u8 *) (&rc)); if (rc < 0) return rc; /* TPM_STS <- 0x40 (commandReady) */ i2c_nuvoton_ready(chip); } else { /* * timeout_b reached - command was * aborted. TIS should now be in idle state - * only TPM_STS_VALID should be set */ if (i2c_nuvoton_read_status(chip) != TPM_STS_VALID) return -EIO; } } } if (tpm_get_timeouts(chip)) return -ENODEV; if (tpm_do_selftest(chip)) return -ENODEV; return tpm_chip_register(chip); }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe37692.38%125.00%
jarkko sakkinenjarkko sakkinen204.91%250.00%
kiran padwalkiran padwal112.70%125.00%
Total407100.00%4100.00%


static int i2c_nuvoton_remove(struct i2c_client *client) { struct device *dev = &(client->dev); struct tpm_chip *chip = dev_get_drvdata(dev); tpm_chip_unregister(chip); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe4097.56%150.00%
jarkko sakkinenjarkko sakkinen12.44%150.00%
Total41100.00%2100.00%

static const struct i2c_device_id i2c_nuvoton_id[] = { {I2C_DRIVER_NAME, 0}, {} }; MODULE_DEVICE_TABLE(i2c, i2c_nuvoton_id); #ifdef CONFIG_OF static const struct of_device_id i2c_nuvoton_of_match[] = { {.compatible = "nuvoton,npct501"}, {.compatible = "winbond,wpct301"}, {}, }; MODULE_DEVICE_TABLE(of, i2c_nuvoton_of_match); #endif static SIMPLE_DEV_PM_OPS(i2c_nuvoton_pm_ops, tpm_pm_suspend, tpm_pm_resume); static struct i2c_driver i2c_nuvoton_driver = { .id_table = i2c_nuvoton_id, .probe = i2c_nuvoton_probe, .remove = i2c_nuvoton_remove, .driver = { .name = I2C_DRIVER_NAME, .pm = &i2c_nuvoton_pm_ops, .of_match_table = of_match_ptr(i2c_nuvoton_of_match), }, }; module_i2c_driver(i2c_nuvoton_driver); MODULE_AUTHOR("Dan Morav (dan.morav@nuvoton.com)"); MODULE_DESCRIPTION("Nuvoton TPM I2C Driver"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
jason gunthorpejason gunthorpe272198.44%233.33%
jarkko sakkinenjarkko sakkinen321.16%350.00%
kiran padwalkiran padwal110.40%116.67%
Total2764100.00%6100.00%
Directory: drivers/char/tpm
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}