diff options
Diffstat (limited to 'drivers/peci/peci-dev.c')
-rw-r--r-- | drivers/peci/peci-dev.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/drivers/peci/peci-dev.c b/drivers/peci/peci-dev.c new file mode 100644 index 000000000000..e0fe09467a80 --- /dev/null +++ b/drivers/peci/peci-dev.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2019 Intel Corporation + +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/peci.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +/* + * A peci_dev represents an peci_adapter ... an PECI or SMBus master, not a + * slave (peci_client) with which messages will be exchanged. It's coupled + * with a character special file which is accessed by user mode drivers. + * + * The list of peci_dev structures is parallel to the peci_adapter lists + * maintained by the driver model, and is updated using bus notifications. + */ +struct peci_dev { + struct list_head list; + struct peci_adapter *adapter; + struct device *dev; + struct cdev cdev; +}; + +#define PECI_MINORS MINORMASK + +static dev_t peci_devt; +static LIST_HEAD(peci_dev_list); +static DEFINE_SPINLOCK(peci_dev_list_lock); + +static struct peci_dev *peci_dev_get_by_minor(uint index) +{ + struct peci_dev *peci_dev; + + spin_lock(&peci_dev_list_lock); + list_for_each_entry(peci_dev, &peci_dev_list, list) { + if (peci_dev->adapter->nr == index) + goto found; + } + peci_dev = NULL; +found: + spin_unlock(&peci_dev_list_lock); + + return peci_dev; +} + +static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter) +{ + struct peci_dev *peci_dev; + + if (adapter->nr >= PECI_MINORS) { + dev_err(&adapter->dev, "Out of device minors (%d)\n", + adapter->nr); + return ERR_PTR(-ENODEV); + } + + peci_dev = kzalloc(sizeof(*peci_dev), GFP_KERNEL); + if (!peci_dev) + return ERR_PTR(-ENOMEM); + peci_dev->adapter = adapter; + + spin_lock(&peci_dev_list_lock); + list_add_tail(&peci_dev->list, &peci_dev_list); + spin_unlock(&peci_dev_list_lock); + + return peci_dev; +} + +static void peci_dev_put(struct peci_dev *peci_dev) +{ + spin_lock(&peci_dev_list_lock); + list_del(&peci_dev->list); + spin_unlock(&peci_dev_list_lock); + kfree(peci_dev); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct peci_dev *peci_dev = peci_dev_get_by_minor(MINOR(dev->devt)); + + if (!peci_dev) + return -ENODEV; + + return sprintf(buf, "%s\n", peci_dev->adapter->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *peci_dev_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(peci_dev); + +static long peci_dev_ioctl(struct file *file, uint iocmd, ulong arg) +{ + struct peci_dev *peci_dev = file->private_data; + void __user *umsg = (void __user *)arg; + struct peci_xfer_msg *xmsg = NULL; + struct peci_xfer_msg uxmsg; + enum peci_cmd cmd; + u8 *msg = NULL; + uint msg_len; + int ret; + + cmd = _IOC_NR(iocmd); + msg_len = _IOC_SIZE(iocmd); + + switch (cmd) { + case PECI_CMD_XFER: + if (msg_len != sizeof(struct peci_xfer_msg)) { + ret = -EFAULT; + break; + } + + if (copy_from_user(&uxmsg, umsg, msg_len)) { + ret = -EFAULT; + break; + } + + xmsg = peci_get_xfer_msg(uxmsg.tx_len, uxmsg.rx_len); + if (IS_ERR(xmsg)) { + ret = PTR_ERR(xmsg); + break; + } + + if (uxmsg.tx_len && + copy_from_user(xmsg->tx_buf, (__u8 __user *)uxmsg.tx_buf, + uxmsg.tx_len)) { + ret = -EFAULT; + break; + } + + xmsg->addr = uxmsg.addr; + xmsg->tx_len = uxmsg.tx_len; + xmsg->rx_len = uxmsg.rx_len; + + ret = peci_command(peci_dev->adapter, cmd, xmsg); + if (!ret && xmsg->rx_len && + copy_to_user((__u8 __user *)uxmsg.rx_buf, xmsg->rx_buf, + xmsg->rx_len)) + ret = -EFAULT; + + break; + + default: + msg = memdup_user(umsg, msg_len); + if (IS_ERR(msg)) { + ret = PTR_ERR(msg); + break; + } + + ret = peci_command(peci_dev->adapter, cmd, msg); + if ((!ret || ret == -ETIMEDOUT) && + copy_to_user(umsg, msg, msg_len)) + ret = -EFAULT; + + break; + } + + peci_put_xfer_msg(xmsg); + kfree(msg); + + return (long)ret; +} + +static int peci_dev_open(struct inode *inode, struct file *file) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + + peci_dev = peci_dev_get_by_minor(iminor(inode)); + if (!peci_dev) + return -ENODEV; + + adapter = peci_get_adapter(peci_dev->adapter->nr); + if (!adapter) + return -ENODEV; + + file->private_data = peci_dev; + + return 0; +} + +static int peci_dev_release(struct inode *inode, struct file *file) +{ + struct peci_dev *peci_dev = file->private_data; + + peci_put_adapter(peci_dev->adapter); + file->private_data = NULL; + + return 0; +} + +static const struct file_operations peci_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = peci_dev_ioctl, + .open = peci_dev_open, + .release = peci_dev_release, + .llseek = no_llseek, +}; + +static struct class *peci_dev_class; + +static int peci_dev_attach_adapter(struct device *dev, void *dummy) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + dev_t devt; + int ret; + + if (dev->type != &peci_adapter_type) + return 0; + + adapter = to_peci_adapter(dev); + peci_dev = peci_dev_alloc(adapter); + if (IS_ERR(peci_dev)) + return PTR_ERR(peci_dev); + + cdev_init(&peci_dev->cdev, &peci_dev_fops); + peci_dev->cdev.owner = THIS_MODULE; + devt = MKDEV(MAJOR(peci_devt), adapter->nr); + + ret = cdev_add(&peci_dev->cdev, devt, 1); + if (ret) + goto err_put_dev; + + /* register this peci device with the driver core */ + peci_dev->dev = device_create(peci_dev_class, &adapter->dev, devt, NULL, + "peci-%d", adapter->nr); + if (IS_ERR(peci_dev->dev)) { + ret = PTR_ERR(peci_dev->dev); + goto err_del_cdev; + } + + dev_info(dev, "cdev of adapter [%s] registered as minor %d\n", + adapter->name, adapter->nr); + + return 0; + +err_del_cdev: + cdev_del(&peci_dev->cdev); +err_put_dev: + peci_dev_put(peci_dev); + + return ret; +} + +static int peci_dev_detach_adapter(struct device *dev, void *dummy) +{ + struct peci_adapter *adapter; + struct peci_dev *peci_dev; + dev_t devt; + + if (dev->type != &peci_adapter_type) + return 0; + + adapter = to_peci_adapter(dev); + peci_dev = peci_dev_get_by_minor(adapter->nr); + if (!peci_dev) + return 0; + + cdev_del(&peci_dev->cdev); + devt = peci_dev->dev->devt; + peci_dev_put(peci_dev); + device_destroy(peci_dev_class, devt); + + dev_info(dev, "cdev of adapter [%s] unregistered\n", adapter->name); + + return 0; +} + +static int peci_dev_notifier_call(struct notifier_block *nb, ulong action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + return peci_dev_attach_adapter(dev, NULL); + case BUS_NOTIFY_DEL_DEVICE: + return peci_dev_detach_adapter(dev, NULL); + } + + return 0; +} + +static struct notifier_block peci_dev_notifier = { + .notifier_call = peci_dev_notifier_call, +}; + +static int __init peci_dev_init(void) +{ + int ret; + + pr_debug("peci /dev entries driver\n"); + + ret = alloc_chrdev_region(&peci_devt, 0, PECI_MINORS, "peci"); + if (ret < 0) { + pr_err("peci: Failed to allocate chr dev region!\n"); + bus_unregister(&peci_bus_type); + goto err; + } + + peci_dev_class = class_create(THIS_MODULE, KBUILD_MODNAME); + if (IS_ERR(peci_dev_class)) { + ret = PTR_ERR(peci_dev_class); + goto err_unreg_chrdev; + } + peci_dev_class->dev_groups = peci_dev_groups; + + /* Keep track of adapters which will be added or removed later */ + ret = bus_register_notifier(&peci_bus_type, &peci_dev_notifier); + if (ret) + goto err_destroy_class; + + /* Bind to already existing adapters right away */ + peci_for_each_dev(NULL, peci_dev_attach_adapter); + + return 0; + +err_destroy_class: + class_destroy(peci_dev_class); +err_unreg_chrdev: + unregister_chrdev_region(peci_devt, PECI_MINORS); +err: + pr_err("%s: Driver Initialization failed\n", __FILE__); + + return ret; +} + +static void __exit peci_dev_exit(void) +{ + bus_unregister_notifier(&peci_bus_type, &peci_dev_notifier); + peci_for_each_dev(NULL, peci_dev_detach_adapter); + class_destroy(peci_dev_class); + unregister_chrdev_region(peci_devt, PECI_MINORS); +} + +module_init(peci_dev_init); +module_exit(peci_dev_exit); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("PECI /dev entries driver"); +MODULE_LICENSE("GPL v2"); |