cregit-Linux how code gets into the kernel

Release 4.11 drivers/char/tpm/tpm_i2c_nuvoton.c

Directory: drivers/char/tpm
/******************************************************************************
 * Nuvoton TPM I2C Device Driver Interface for WPCT301/NPCT501/NPCT6XX,
 * 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 <linux/of_device.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 OF_IS_TPM2 ((void *)1)

#define I2C_IS_TPM2 1


struct priv_data {
	
int irq;
	
unsigned int intrs;
	
wait_queue_head_t read_queue;
};


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 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 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->dev.parent); s32 status; u8 data; status = i2c_nuvoton_read_buf(client, TPM_STS, 1, &data); if (status <= 0) { dev_err(&chip->dev, "%s() error return %d\n", __func__, status); data = TPM_STS_ERR_VAL; } return data; }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe74100.00%2100.00%
Total74100.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 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->dev.parent); 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->dev, "%s() fail to write TPM_STS.commandReady\n", __func__); }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe56100.00%2100.00%
Total56100.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->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 Gunthorpe99100.00%1100.00%
Total99100.00%1100.00%

/* * WPCT301/NPCT501/NPCT6XX 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 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->flags & TPM_CHIP_FLAG_IRQ) && queue) { s32 rc; struct priv_data *priv = dev_get_drvdata(&chip->dev); unsigned int cur_intrs = priv->intrs; enable_irq(priv->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->dev, "%s(%02x, %02x) -> timeout\n", __func__, mask, value); return -ETIMEDOUT; }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe20294.84%250.00%
Christophe Ricard115.16%250.00%
Total213100.00%4100.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 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) { struct priv_data *priv = dev_get_drvdata(&chip->dev); s32 rc; int burst_count, bytes2read, size = 0; while (size < count && i2c_nuvoton_wait_for_data_avail(chip, chip->timeout_c, &priv->read_queue) == 0) { burst_count = i2c_nuvoton_get_burstcount(client, chip); if (burst_count < 0) { dev_err(&chip->dev, "%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->dev, "%s() fail on i2c_nuvoton_read_buf()=%d\n", __func__, rc); return -EIO; } dev_dbg(&chip->dev, "%s(%d):", __func__, bytes2read); size += bytes2read; } return size; }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe17092.39%250.00%
Christophe Ricard147.61%250.00%
Total184100.00%4100.00%

/* Read TPM command results */
static int i2c_nuvoton_recv(struct tpm_chip *chip, u8 *buf, size_t count) { struct priv_data *priv = dev_get_drvdata(&chip->dev); struct device *dev = chip->dev.parent; 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->timeout_c, &priv->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->timeout_c, NULL)) { dev_err(dev, "%s() error left over data\n", __func__); size = -ETIMEDOUT; continue; } break; } i2c_nuvoton_ready(chip); dev_dbg(&chip->dev, "%s() -> %d\n", __func__, size); return size; }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe36996.34%250.00%
Christophe Ricard143.66%250.00%
Total383100.00%4100.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 priv_data *priv = dev_get_drvdata(&chip->dev); struct device *dev = chip->dev.parent; 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->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->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->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), &priv->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 Gunthorpe49697.25%250.00%
Christophe Ricard142.75%250.00%
Total510100.00%4100.00%


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

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe21100.00%1100.00%
Total21100.00%1100.00%

static const struct tpm_class_ops tpm_i2c = { .flags = TPM_OPS_AUTO_STARTUP, .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 = dev_get_drvdata(&chip->dev); priv->intrs++; wake_up(&priv->read_queue); disable_irq_nosync(priv->irq); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe4987.50%125.00%
Christophe Ricard712.50%375.00%
Total56100.00%4100.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 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; struct priv_data *priv; 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); priv = devm_kzalloc(dev, sizeof(struct priv_data), GFP_KERNEL); if (!priv) return -ENOMEM; if (dev->of_node) { const struct of_device_id *of_id; of_id = of_match_device(dev->driver->of_match_table, dev); if (of_id && of_id->data == OF_IS_TPM2) chip->flags |= TPM_CHIP_FLAG_TPM2; } else if (id->driver_data == I2C_IS_TPM2) chip->flags |= TPM_CHIP_FLAG_TPM2; init_waitqueue_head(&priv->read_queue); /* Default timeouts */ chip->timeout_a = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); chip->timeout_b = msecs_to_jiffies(TPM_I2C_LONG_TIMEOUT); chip->timeout_c = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); chip->timeout_d = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); dev_set_drvdata(&chip->dev, priv); /* * 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 */ priv->irq = client->irq; if (client->irq) { dev_dbg(dev, "%s() priv->irq\n", __func__); rc = devm_request_irq(dev, client->irq, i2c_nuvoton_int_handler, IRQF_TRIGGER_LOW, dev_name(&chip->dev), chip); if (rc) { dev_err(dev, "%s() Unable to request irq: %d for use\n", __func__, priv->irq); priv->irq = 0; } else { chip->flags |= TPM_CHIP_FLAG_IRQ; /* 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->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; } } } return tpm_chip_register(chip); }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe37487.38%333.33%
Christophe Ricard235.37%333.33%
Jarkko Sakkinen163.74%111.11%
Andrew Zamansky81.87%111.11%
Kiran Padwal71.64%111.11%
Total428100.00%9100.00%


static int i2c_nuvoton_remove(struct i2c_client *client) { struct tpm_chip *chip = i2c_get_clientdata(client); tpm_chip_unregister(chip); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Jason Gunthorpe2689.66%133.33%
Christophe Ricard26.90%133.33%
Jarkko Sakkinen13.45%133.33%
Total29100.00%3100.00%

static const struct i2c_device_id i2c_nuvoton_id[] = { {"tpm_i2c_nuvoton"}, {"tpm2_i2c_nuvoton", .driver_data = I2C_IS_TPM2}, {} }; 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"}, {.compatible = "nuvoton,npct601", .data = OF_IS_TPM2}, {}, }; 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 = "tpm_i2c_nuvoton", .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 Gunthorpe272195.68%650.00%
Christophe Ricard913.20%325.00%
Jarkko Sakkinen170.60%18.33%
Andrew Zamansky80.28%18.33%
Kiran Padwal70.25%18.33%
Total2844100.00%12100.00%
Directory: drivers/char/tpm
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.