diff options
author | Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com> | 2022-03-02 23:15:40 +0300 |
---|---|---|
committer | Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com> | 2022-03-24 14:20:03 +0300 |
commit | 6eca78d14f7aef61a149d9f4d3a3ec818b98f18e (patch) | |
tree | ce560348e1678396c8a21c5999ca3948c2b8ba5f /drivers/i3c | |
parent | e18bf5533c7bdde6392a546372cbf3bb5fd36497 (diff) | |
download | linux-6eca78d14f7aef61a149d9f4d3a3ec818b98f18e.tar.xz |
i3c: mctp: add MCTP I3C driver
The driver was implemented in order to provide userspace with fops
functions needed to process MCTP packets over I3C.
Signed-off-by: Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>
Diffstat (limited to 'drivers/i3c')
-rw-r--r-- | drivers/i3c/Kconfig | 2 | ||||
-rw-r--r-- | drivers/i3c/Makefile | 1 | ||||
-rw-r--r-- | drivers/i3c/mctp/Kconfig | 6 | ||||
-rw-r--r-- | drivers/i3c/mctp/Makefile | 2 | ||||
-rw-r--r-- | drivers/i3c/mctp/i3c-mctp.c | 349 |
5 files changed, 360 insertions, 0 deletions
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 01642768ab5f..b807482b7c1d 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -21,6 +21,8 @@ menuconfig I3C if I3C +source "drivers/i3c/mctp/Kconfig" + config I3CDEV tristate "I3C device interface" depends on I3C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 606d422841b2..b9700b750bcc 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -3,3 +3,4 @@ i3c-y := device.o master.o obj-$(CONFIG_I3C) += i3c.o obj-$(CONFIG_I3CDEV) += i3cdev.o obj-$(CONFIG_I3C) += master/ +obj-$(CONFIG_I3C) += mctp/ diff --git a/drivers/i3c/mctp/Kconfig b/drivers/i3c/mctp/Kconfig new file mode 100644 index 000000000000..e0b9743b4c6d --- /dev/null +++ b/drivers/i3c/mctp/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +config I3C_MCTP + tristate "I3C MCTP driver" + depends on I3C +help + Say yes here to enable the I3C MCTP driver. diff --git a/drivers/i3c/mctp/Makefile b/drivers/i3c/mctp/Makefile new file mode 100644 index 000000000000..b6e19ada2916 --- /dev/null +++ b/drivers/i3c/mctp/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_I3C_MCTP) += i3c-mctp.o diff --git a/drivers/i3c/mctp/i3c-mctp.c b/drivers/i3c/mctp/i3c-mctp.c new file mode 100644 index 000000000000..b0e381456404 --- /dev/null +++ b/drivers/i3c/mctp/i3c-mctp.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2022 Intel Corporation.*/ + +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/preempt.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <linux/i3c/device.h> + +#define I3C_MCTP_MINORS 32 +#define CCC_DEVICE_STATUS_PENDING_INTR(x) (((x) & GENMASK(3, 0)) >> 0) +#define POLLING_TIMEOUT_MS 50 +#define MCTP_INTERRUPT_NUMBER 1 + +struct i3c_mctp { + struct i3c_device *i3c; + struct cdev cdev; + struct device *dev; + struct delayed_work polling_work; + int id; + wait_queue_head_t *wait_queue; + bool transfer_rdy; + /* + * Prevent simultaneous access to the transfer_rdy + * flag which signalizes about read transfer readiness. + */ + spinlock_t transfer_rdy_lock; + /* + * Restrict an access to the /dev descriptor to one + * user at a time. + */ + spinlock_t device_file_lock; + int device_open; +}; + +static struct class *i3c_mctp_class; +static dev_t i3c_mctp_devt; +static DEFINE_IDA(i3c_mctp_ida); + +static void i3c_mctp_polling_work(struct work_struct *work) +{ + struct i3c_mctp *priv = container_of(to_delayed_work(work), struct i3c_mctp, polling_work); + struct i3c_device *i3cdev = priv->i3c; + struct i3c_device_info info; + int ret; + + i3c_device_get_info(i3cdev, &info); + ret = i3c_device_getstatus_ccc(i3cdev, &info); + if (ret) + return; + + if (CCC_DEVICE_STATUS_PENDING_INTR(info.status) != MCTP_INTERRUPT_NUMBER) + return; + + spin_lock(&priv->transfer_rdy_lock); + priv->transfer_rdy = true; + spin_unlock(&priv->transfer_rdy_lock); + + wake_up_all(priv->wait_queue); + + schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS)); +} + +static ssize_t i3c_mctp_write(struct file *file, const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct i3c_mctp *priv = file->private_data; + struct i3c_device *i3c = priv->i3c; + struct i3c_priv_xfer xfers = { + .rnw = false, + .len = count, + }; + u8 *data; + int ret; + + data = memdup_user(buf, count); + if (IS_ERR(data)) + return PTR_ERR(data); + + xfers.data.out = data; + + ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); + kfree(data); + return ret ?: count; +} + +static ssize_t i3c_mctp_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) +{ + struct i3c_mctp *priv = file->private_data; + struct i3c_device *i3c = priv->i3c; + struct i3c_priv_xfer xfers = { + .rnw = true, + .len = count, + }; + u8 *data; + int ret; + + data = kzalloc(count, GFP_KERNEL); + if (!data) + return -ENOMEM; + + xfers.data.in = data; + + if (!priv->transfer_rdy) { + ret = -EFAULT; + goto out; + } + + ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); + if (ret) + goto out; + + if (copy_to_user(buf, data, xfers.len)) { + ret = -EFAULT; + goto out; + } + spin_lock(&priv->transfer_rdy_lock); + priv->transfer_rdy = false; + spin_unlock(&priv->transfer_rdy_lock); + +out: + kfree(data); + return ret ?: xfers.len; +} + +static int i3c_mctp_open(struct inode *inode, struct file *file) +{ + struct i3c_mctp *priv = container_of(inode->i_cdev, struct i3c_mctp, cdev); + + spin_lock(&priv->device_file_lock); + if (priv->device_open) { + spin_unlock(&priv->device_file_lock); + return -EBUSY; + } + priv->device_open++; + spin_unlock(&priv->device_file_lock); + + file->private_data = priv; + init_waitqueue_head(priv->wait_queue); + + return 0; +} + +static int i3c_mctp_release(struct inode *inode, struct file *file) +{ + struct i3c_mctp *priv = file->private_data; + + spin_lock(&priv->device_file_lock); + priv->device_open--; + spin_unlock(&priv->device_file_lock); + + file->private_data = NULL; + + return 0; +} + +static __poll_t i3c_mctp_poll(struct file *file, struct poll_table_struct *pt) +{ + struct i3c_mctp *priv = file->private_data; + __poll_t ret = 0; + + poll_wait(file, priv->wait_queue, pt); + + return priv->transfer_rdy ? EPOLLIN : ret; +} + +static const struct file_operations i3c_mctp_fops = { + .owner = THIS_MODULE, + .read = i3c_mctp_read, + .write = i3c_mctp_write, + .poll = i3c_mctp_poll, + .open = i3c_mctp_open, + .release = i3c_mctp_release, +}; + +static struct i3c_mctp *i3c_mctp_alloc(struct i3c_device *i3c) +{ + struct i3c_mctp *priv; + int id; + + priv = devm_kzalloc(i3cdev_to_dev(i3c), sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + id = ida_alloc(&i3c_mctp_ida, GFP_KERNEL); + if (id < 0) { + pr_err("i3c_mctp: no minor number available!\n"); + return ERR_PTR(id); + } + + priv->id = id; + priv->i3c = i3c; + priv->wait_queue = devm_kzalloc(i3cdev_to_dev(i3c), sizeof(wait_queue_head_t), GFP_KERNEL); + if (!priv->wait_queue) { + ida_free(&i3c_mctp_ida, id); + return ERR_PTR(-ENOMEM); + } + + spin_lock_init(&priv->device_file_lock); + spin_lock_init(&priv->transfer_rdy_lock); + + return priv; +} + +static void i3c_mctp_ibi_handler(struct i3c_device *dev, const struct i3c_ibi_payload *payload) +{ + struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(dev)); + + spin_lock(&priv->device_file_lock); + if (!priv->device_open) { + spin_unlock(&priv->device_file_lock); + return; + } + spin_unlock(&priv->device_file_lock); + + wake_up_all(priv->wait_queue); + spin_lock(&priv->transfer_rdy_lock); + priv->transfer_rdy = true; + spin_unlock(&priv->transfer_rdy_lock); +} + +static int i3c_mctp_init(struct i3c_driver *drv) +{ + int ret; + + /* Dynamically request unused major number */ + ret = alloc_chrdev_region(&i3c_mctp_devt, 0, I3C_MCTP_MINORS, "i3c-mctp"); + if (ret) + goto out; + + /* Create a class to populate sysfs entries*/ + i3c_mctp_class = class_create(THIS_MODULE, "i3c-mctp"); + if (IS_ERR(i3c_mctp_class)) { + ret = PTR_ERR(i3c_mctp_class); + goto out_unreg_chrdev; + } + + i3c_driver_register(drv); + + return 0; + +out_unreg_chrdev: + unregister_chrdev_region(i3c_mctp_devt, I3C_MCTP_MINORS); +out: + pr_err("i3c_mctp: driver initialisation failed\n"); + return ret; +} + +static void i3c_mctp_free(struct i3c_driver *drv) +{ + i3c_driver_unregister(drv); + class_destroy(i3c_mctp_class); + unregister_chrdev_region(i3c_mctp_devt, I3C_MCTP_MINORS); +} + +static int i3c_mctp_enable_ibi(struct i3c_device *i3cdev) +{ + struct i3c_ibi_setup ibireq = { + .handler = i3c_mctp_ibi_handler, + .max_payload_len = 2, + .num_slots = 10, + }; + int ret; + + ret = i3c_device_request_ibi(i3cdev, &ibireq); + if (ret) + return ret; + ret = i3c_device_enable_ibi(i3cdev); + if (ret) + i3c_device_free_ibi(i3cdev); + + return ret; +} + +static int i3c_mctp_probe(struct i3c_device *i3cdev) +{ + struct i3c_mctp *priv; + struct device *dev = i3cdev_to_dev(i3cdev); + int ret; + + priv = i3c_mctp_alloc(i3cdev); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + cdev_init(&priv->cdev, &i3c_mctp_fops); + + priv->cdev.owner = THIS_MODULE; + ret = cdev_add(&priv->cdev, MKDEV(MAJOR(i3c_mctp_devt), priv->id), 1); + if (ret) + goto error_cdev; + + /* register this i3c device with the driver core */ + priv->dev = device_create(i3c_mctp_class, dev, + MKDEV(MAJOR(i3c_mctp_devt), priv->id), + NULL, "i3c-mctp-%s", dev_name(dev)); + if (IS_ERR(priv->dev)) { + ret = PTR_ERR(priv->dev); + goto error; + } + + dev_set_drvdata(i3cdev_to_dev(i3cdev), priv); + + if (i3c_mctp_enable_ibi(i3cdev)) { + INIT_DELAYED_WORK(&priv->polling_work, i3c_mctp_polling_work); + schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS)); + } + + return 0; + +error: + cdev_del(&priv->cdev); +error_cdev: + put_device(dev); + return ret; +} + +static void i3c_mctp_remove(struct i3c_device *i3cdev) +{ + struct i3c_mctp *priv = dev_get_drvdata(i3cdev_to_dev(i3cdev)); + + i3c_device_disable_ibi(i3cdev); + i3c_device_free_ibi(i3cdev); + + device_destroy(i3c_mctp_class, MKDEV(MAJOR(i3c_mctp_devt), priv->id)); + cdev_del(&priv->cdev); + ida_free(&i3c_mctp_ida, priv->id); +} + +static const struct i3c_device_id i3c_mctp_ids[] = { + I3C_CLASS(0xCC, 0x0), + { }, +}; + +static struct i3c_driver i3c_mctp_drv = { + .driver.name = "i3c-mctp", + .id_table = i3c_mctp_ids, + .probe = i3c_mctp_probe, + .remove = i3c_mctp_remove, +}; + +module_driver(i3c_mctp_drv, i3c_mctp_init, i3c_mctp_free); +MODULE_AUTHOR("Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>"); +MODULE_DESCRIPTION("I3C MCTP driver"); +MODULE_LICENSE("GPL"); |