| /* |
| * Citadel transport driver |
| * |
| * Copyright (C) 2017 Fernando Lugo <flugo@google.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. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioctl.h> |
| #include <linux/fs.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/poll.h> |
| #include <linux/uaccess.h> |
| #include <linux/cdev.h> |
| #include <linux/spi/spi.h> |
| #include <linux/citadel.h> |
| #include <linux/wait.h> |
| |
| #define CITADEL_TPM_READ 0x80000000 |
| |
| #define MAX_DATA_SIZE 2044 |
| #define CITADEL_MAX_DEVICES 4 |
| #define CITADEL_TPM_TIMEOUT_MS 10 |
| |
| struct citadel_data { |
| dev_t devt; |
| struct cdev cdev; |
| struct spi_device *spi; |
| int ctdl_ap_irq; |
| wait_queue_head_t waitq; |
| int ctdl_rst; |
| atomic_t users; |
| void *tx_buf; |
| void *rx_buf; |
| }; |
| |
| static struct class *citadel_class; |
| static dev_t citadel_devt; |
| |
| static int citadel_wait_cmd_done(struct spi_device *spi) |
| { |
| struct spi_message m; |
| int ret; |
| u8 val; |
| unsigned long to = jiffies + 1 + /* at least one jiffy */ |
| msecs_to_jiffies(CITADEL_TPM_TIMEOUT_MS); |
| struct spi_transfer spi_xfer = { |
| .rx_buf = &val, |
| .len = 1, |
| .cs_change = 1, |
| }; |
| |
| /* |
| * We have sent the initial four-byte command to Citadel on MOSI, and |
| * now we're waiting for bit0 of the MISO byte to be set, indicating |
| * that we can continue with the rest of the transaction. If that bit |
| * is not immediately set, we'll keep sending one more don't-care byte |
| * on MOSI just to read MISO until it is. If Citadel is awake and |
| * functioning correctly its hardware implementation should always |
| * return 0x00 while it's thinking and return 0x01 when ready to |
| * continue. However, there are a couple of ways this can fail. First, |
| * if Citadel is in deep sleep, it should send 0x5A on MISO until it |
| * wakes up. This may take ~40ms or so but it's not unexpected, so if |
| * we time out we just give up and try again. Second, if Citadel |
| * unexpectedly reboots, it will probably return 0xFF or 0xDF, which |
| * will exit the loop but we shouldn't continue the transaction because |
| * Citadel won't know what's going on. |
| */ |
| do { |
| if (time_after(jiffies, to)) { |
| dev_warn(&spi->dev, "Citadel SPI timed out\n"); |
| return -EBUSY; |
| } |
| spi_message_init(&m); |
| spi_message_add_tail(&spi_xfer, &m); |
| ret = spi_sync_locked(spi, &m); |
| if (ret) |
| return ret; |
| } while (!val); |
| |
| /* Return EAGAIN if unexpected bytes were received. */ |
| return val & 0x01 ? 0 : -EAGAIN; |
| } |
| |
| static int citadel_tpm_datagram(struct citadel_data *citadel, |
| struct citadel_ioc_tpm_datagram *dg) |
| { |
| int is_read; |
| int citadel_is_awake; |
| int ret; |
| int ignore_result = 0; |
| struct spi_device *spi = citadel->spi; |
| u32 command; |
| u32 response; |
| struct spi_message m; |
| struct spi_transfer spi_xfer = { |
| .tx_buf = &command, |
| .rx_buf = &response, |
| .len = 4, |
| .cs_change = 1, |
| }; |
| |
| /* Read == from SPI, to userland. */ |
| is_read = dg->command & CITADEL_TPM_READ; |
| |
| /* Lock the SPI bus until we're completely done */ |
| spi_bus_lock(spi->master); |
| |
| /* The command must be big-endian */ |
| command = cpu_to_be32(dg->command); |
| |
| /* Prepare to send the command */ |
| spi_message_init(&m); |
| spi_message_add_tail(&spi_xfer, &m); |
| |
| /* Send the command out */ |
| ret = spi_sync_locked(spi, &m); |
| if (ret) |
| goto exit; |
| |
| /* Verify that citadel is idle. If it isn't, deassert CS and return |
| * -EAGAIN. 0xdf is what citadel sends when the SPI FIFO is empty, |
| * and it is in the TPM wait mode. Once a command is sent to Citadel, |
| * the last bit shows whether or not Citadel is ready to send the |
| * response. This typically happens by a 0x01 byte, but may be |
| * preceded by several 0x00 bytes while Citadel does any necessary |
| * work. */ |
| citadel_is_awake = response == be32_to_cpu(0xdfdfdfde); |
| |
| if (citadel_is_awake) { |
| /* Wait for the reply bit to go high */ |
| ret = citadel_wait_cmd_done(spi); |
| /* Reset CS in the case of unexpected bytes. */ |
| if (ret) |
| citadel_is_awake = 0; |
| } |
| |
| /* TODO: If there's no data, how do we disable the CS? */ |
| if (!dg->len || !citadel_is_awake) { |
| /* For now, just transfer one more byte and throw it away */ |
| is_read = 1; |
| dg->len = 1; |
| ignore_result = 1; |
| } |
| |
| /* Now we can transfer the data */ |
| spi_xfer.cs_change = 0; |
| spi_xfer.len = dg->len; |
| if (is_read) { |
| spi_xfer.rx_buf = citadel->rx_buf; |
| spi_xfer.tx_buf = NULL; |
| } else { |
| spi_xfer.rx_buf = NULL; |
| spi_xfer.tx_buf = citadel->tx_buf; |
| |
| if (copy_from_user(citadel->tx_buf, |
| (void __user *)dg->buf, dg->len)) { |
| ret = -EFAULT; |
| goto exit; |
| } |
| } |
| |
| spi_message_init(&m); |
| spi_message_add_tail(&spi_xfer, &m); |
| ret = spi_sync_locked(spi, &m); |
| if (ret) |
| goto exit; |
| |
| /* This condition typically happens when citadel is asleep. Toggling |
| * CS should be sufficient to wake up citadel, but some time needs to |
| * pass before it will be ready. */ |
| if (!citadel_is_awake) |
| ret = -EAGAIN; |
| |
| if (ignore_result) |
| goto exit; |
| |
| if (is_read && copy_to_user((void __user *)dg->buf, |
| citadel->rx_buf, dg->len)) |
| ret = -EFAULT; |
| |
| exit: |
| spi_bus_unlock(spi->master); |
| |
| return ret; |
| } |
| |
| static int citadel_reset(struct citadel_data *citadel) |
| { |
| /* Synchronize with the datagrams by locking the SPI bus */ |
| struct spi_device *spi = citadel->spi; |
| spi_bus_lock(spi->master); |
| |
| /* Assert reset for at least 3ms after VDDIOM is stable; 10ms is safe */ |
| gpio_set_value(citadel->ctdl_rst, 1); |
| msleep(10); |
| |
| /* Clear reset and wait for Citadel to become functional */ |
| gpio_set_value(citadel->ctdl_rst, 0); |
| msleep(100); |
| |
| spi_bus_unlock(spi->master); |
| return 0; |
| } |
| |
| static long |
| citadel_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| u32 tmp; |
| struct citadel_data *citadel = filp->private_data; |
| struct citadel_ioc_tpm_datagram dg; |
| |
| /* check magic */ |
| if (_IOC_TYPE(cmd) != CITADEL_IOC_MAGIC) |
| return -ENOTTY; |
| switch (cmd) { |
| case CITADEL_IOC_TPM_DATAGRAM: |
| tmp = _IOC_SIZE(cmd); |
| if (tmp != sizeof(dg)) |
| return -EINVAL; |
| |
| if (copy_from_user(&dg, (void __user *)arg, sizeof(dg))) |
| return -EFAULT; |
| |
| if (dg.len > MAX_DATA_SIZE) |
| return -E2BIG; |
| |
| /* translate to spi_message, execute */ |
| return citadel_tpm_datagram(citadel, &dg); |
| case CITADEL_IOC_RESET: |
| return citadel_reset(citadel); |
| } |
| return -EINVAL; |
| } |
| |
| static ssize_t |
| citadel_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) |
| { |
| int ret; |
| size_t c = 0; |
| size_t len; |
| struct citadel_data *citadel = filp->private_data; |
| |
| while (count) { |
| len = count > MAX_DATA_SIZE ? MAX_DATA_SIZE : count; |
| ret = spi_read(citadel->spi, citadel->rx_buf, len); |
| if (ret) |
| return ret; |
| ret = copy_to_user(buf + c, citadel->rx_buf, len); |
| if (ret) |
| return -EFAULT; |
| c += len; |
| count -= len; |
| } |
| |
| return c; |
| } |
| |
| static ssize_t |
| citadel_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *f_pos) |
| { |
| int ret; |
| size_t c = 0; |
| size_t len; |
| struct citadel_data *citadel = filp->private_data; |
| |
| while (count) { |
| len = count > MAX_DATA_SIZE ? MAX_DATA_SIZE : count; |
| ret = copy_from_user(citadel->tx_buf, buf + c, len); |
| if (ret) |
| return -EFAULT; |
| ret = spi_write(citadel->spi, citadel->tx_buf, len); |
| if (ret) |
| return ret; |
| c += len; |
| count -= len; |
| } |
| |
| return c; |
| } |
| |
| static int citadel_open(struct inode *inode, struct file *filp) |
| { |
| struct citadel_data *citadel; |
| |
| citadel = container_of(inode->i_cdev, struct citadel_data, cdev); |
| |
| /* we only support 1 user at the same time */ |
| if (!atomic_add_unless(&citadel->users, 1, 1)) |
| return -EBUSY; |
| |
| filp->private_data = citadel; |
| nonseekable_open(inode, filp); |
| |
| return 0; |
| } |
| |
| static unsigned int citadel_poll(struct file *filp, poll_table *wait) |
| { |
| struct citadel_data *citadel = filp->private_data; |
| poll_wait(filp, &citadel->waitq, wait); |
| return gpio_get_value(citadel->ctdl_ap_irq) ? POLLIN : 0; |
| } |
| |
| static int citadel_release(struct inode *inode, struct file *filp) |
| { |
| struct citadel_data *citadel = filp->private_data; |
| |
| atomic_dec(&citadel->users); |
| |
| return 0; |
| } |
| |
| static const struct file_operations citadel_fops = { |
| .owner = THIS_MODULE, |
| .write = citadel_write, |
| .read = citadel_read, |
| .open = citadel_open, |
| .poll = citadel_poll, |
| .release = citadel_release, |
| .unlocked_ioctl = citadel_ioctl, |
| .llseek = no_llseek, |
| }; |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id citadel_dt_ids[] = { |
| { .compatible = "google,citadel" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, citadel_dt_ids); |
| #endif |
| |
| static irqreturn_t citadel_irq_handler(int irq, void *handle) |
| { |
| struct citadel_data *citadel = (struct citadel_data *)handle; |
| wake_up_interruptible(&citadel->waitq); |
| return IRQ_HANDLED; |
| } |
| |
| static int citadel_request_named_gpio(struct citadel_data *citadel, |
| const char *label, int *gpio) |
| { |
| struct device *dev = &citadel->spi->dev; |
| struct device_node *np = dev->of_node; |
| int rc = of_get_named_gpio(np, label, 0); |
| |
| if (rc < 0) { |
| dev_err(dev, "failed to get '%s'\n", label); |
| return rc; |
| } |
| *gpio = rc; |
| |
| rc = devm_gpio_request(dev, *gpio, label); |
| if (rc) { |
| dev_err(dev, "failed to request gpio %d\n", *gpio); |
| return rc; |
| } |
| dev_dbg(dev, "%s %d\n", label, *gpio); |
| |
| return 0; |
| } |
| |
| static int citadel_probe(struct spi_device *spi) |
| { |
| struct device *dev; |
| struct cdev cdev; |
| struct citadel_data *citadel; |
| int ret; |
| dev_t devt; |
| u32 minor; |
| |
| /* use chip select as minor */ |
| minor = (u32)spi->chip_select; |
| if (minor >= CITADEL_MAX_DEVICES) { |
| dev_err(&spi->dev, "minor %u out of boundaries\n", minor); |
| return -ENXIO; |
| } |
| |
| citadel = kmalloc(sizeof(*citadel), GFP_KERNEL); |
| if (!citadel) |
| return -ENOMEM; |
| |
| /* init citadel structure */ |
| citadel->tx_buf = (void *)__get_free_pages(GFP_KERNEL, |
| get_order(MAX_DATA_SIZE)); |
| citadel->rx_buf = (void *)__get_free_pages(GFP_KERNEL, |
| get_order(MAX_DATA_SIZE)); |
| if (!citadel->tx_buf || !citadel->rx_buf) { |
| ret = -ENOMEM; |
| goto free_citadel; |
| } |
| init_waitqueue_head(&citadel->waitq); |
| atomic_set(&citadel->users, 0); |
| citadel->spi = spi; |
| devt = MKDEV(MAJOR(citadel_devt), minor); |
| citadel->devt = devt; |
| spi_set_drvdata(spi, citadel); |
| |
| /* setup ctdl_ap_irq */ |
| ret = citadel_request_named_gpio(citadel, "citadel,ctdl_ap_irq", |
| &citadel->ctdl_ap_irq); |
| if (ret) { |
| dev_err(&spi->dev, |
| "citadel_request_named_gpio " |
| "citadel,ctdl_ap_irq failed.\n"); |
| goto free_citadel; |
| } |
| |
| ret = devm_request_irq(&citadel->spi->dev, |
| gpio_to_irq(citadel->ctdl_ap_irq), |
| citadel_irq_handler, |
| IRQF_NO_SUSPEND | IRQF_TRIGGER_RISING | |
| IRQF_ONESHOT, |
| dev_name(&spi->dev), |
| citadel); |
| if (ret) { |
| dev_err(&spi->dev, |
| "devm_request_irq citadel,ctdl_ap_irq failed.\n"); |
| goto free_citadel; |
| } |
| |
| enable_irq_wake(gpio_to_irq(citadel->ctdl_ap_irq)); |
| |
| /* setup ctdl_rst */ |
| ret = citadel_request_named_gpio(citadel, "citadel,ctdl_rst", |
| &citadel->ctdl_rst); |
| if (ret) { |
| dev_err(&spi->dev, |
| "citadel_request_named_gpio " |
| "citadel,ctdl_rst failed.\n"); |
| goto free_citadel; |
| } |
| |
| gpio_direction_output(citadel->ctdl_rst, 0); |
| |
| /* create the device */ |
| dev = device_create(citadel_class, &spi->dev, devt, NULL, |
| "citadel%u", minor); |
| if (IS_ERR(dev)) { |
| ret = PTR_ERR(dev); |
| goto free_citadel; |
| } |
| |
| cdev_init(&citadel->cdev, &citadel_fops); |
| cdev.owner = THIS_MODULE; |
| ret = cdev_add(&citadel->cdev, citadel_devt, 1); |
| if (ret) |
| goto destroy_device; |
| |
| return 0; |
| |
| destroy_device: |
| device_destroy(citadel_class, devt); |
| free_citadel: |
| free_pages((unsigned long)citadel->rx_buf, get_order(MAX_DATA_SIZE)); |
| free_pages((unsigned long)citadel->tx_buf, get_order(MAX_DATA_SIZE)); |
| kfree(citadel); |
| return ret; |
| } |
| |
| static int citadel_remove(struct spi_device *spi) |
| { |
| struct citadel_data *citadel = spi_get_drvdata(spi); |
| |
| cdev_del(&citadel->cdev); |
| device_destroy(citadel_class, citadel->devt); |
| free_pages((unsigned long)citadel->rx_buf, get_order(MAX_DATA_SIZE)); |
| free_pages((unsigned long)citadel->tx_buf, get_order(MAX_DATA_SIZE)); |
| kfree(citadel); |
| |
| return 0; |
| } |
| |
| static struct spi_driver citadel_spi_driver = { |
| .driver = { |
| .name = "citadel", |
| .of_match_table = of_match_ptr(citadel_dt_ids), |
| }, |
| .probe = citadel_probe, |
| .remove = citadel_remove, |
| }; |
| |
| static int __init citadel_init(void) |
| { |
| int ret; |
| |
| ret = alloc_chrdev_region(&citadel_devt, 0, CITADEL_MAX_DEVICES, |
| "citadel"); |
| if (ret) { |
| pr_err("%s: failed to alloc cdev region %d\n", __func__, ret); |
| return ret; |
| } |
| |
| citadel_class = class_create(THIS_MODULE, "citadel"); |
| if (IS_ERR(citadel_class)) { |
| unregister_chrdev_region(citadel_devt, 1); |
| return PTR_ERR(citadel_class); |
| } |
| |
| ret = spi_register_driver(&citadel_spi_driver); |
| if (ret < 0) { |
| class_destroy(citadel_class); |
| unregister_chrdev_region(citadel_devt, 1); |
| } |
| return ret; |
| } |
| |
| static void __exit citadel_exit(void) |
| { |
| spi_unregister_driver(&citadel_spi_driver); |
| class_destroy(citadel_class); |
| unregister_chrdev_region(citadel_devt, 1); |
| } |
| |
| module_init(citadel_init); |
| module_exit(citadel_exit); |
| |
| MODULE_AUTHOR("Fernando Lugo, <flugo@google.com>"); |
| MODULE_DESCRIPTION("CITADEL TPM driver"); |
| MODULE_LICENSE("GPL"); |