From 426a368a8790b0561ef81bbd076fcc9c5e7e5ac1 Mon Sep 17 00:00:00 2001 From: Oleksandr Shulzhenko Date: Tue, 10 May 2022 17:27:02 +0200 Subject: i3c: mctp: Extend MCTP o. I3C driver with the TX/RX functions Extend the driver with the functionality necessary to send and receive MCTP over I3C packets by another component in kernel: - i3c_mctp_client concept needed to implement packet queue so that the driver can manage and classify packets for several clients respectively; - the function that provides MCTP target EID; - the functions to alloc and free mctp_i3c_packet; - the functions to send and receive MCTP packets over I3C. Signed-off-by: Oleksandr Shulzhenko --- drivers/i3c/mctp/i3c-mctp.c | 279 ++++++++++++++++++++++++++++++-------- include/linux/i3c/mctp/i3c-mctp.h | 38 ++++++ 2 files changed, 257 insertions(+), 60 deletions(-) create mode 100644 include/linux/i3c/mctp/i3c-mctp.h diff --git a/drivers/i3c/mctp/i3c-mctp.c b/drivers/i3c/mctp/i3c-mctp.c index 560884ccc51a..fbfcd1310901 100644 --- a/drivers/i3c/mctp/i3c-mctp.c +++ b/drivers/i3c/mctp/i3c-mctp.c @@ -6,16 +6,21 @@ #include #include #include +#include +#include #include #include #include #include +#include + #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 +#define RX_RING_COUNT 16 struct i3c_mctp { struct i3c_device *i3c; @@ -23,29 +28,128 @@ struct i3c_mctp { 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; + /* Currently only one userspace client is supported */ + struct i3c_mctp_client *default_client; +}; + +struct i3c_mctp_client { + struct i3c_mctp *priv; + struct ptr_ring rx_queue; + wait_queue_head_t wait_queue; }; static struct class *i3c_mctp_class; static dev_t i3c_mctp_devt; static DEFINE_IDA(i3c_mctp_ida); +static struct kmem_cache *packet_cache; + +/** + * i3c_mctp_packet_alloc() - allocates i3c_mctp_packet + * + * @flags: the type of memory to allocate + * + * Allocates i3c_mctp_packet via slab allocation + * Return: pointer to the packet, NULL if some error occurred + */ +void *i3c_mctp_packet_alloc(gfp_t flags) +{ + return kmem_cache_alloc(packet_cache, flags); +} +EXPORT_SYMBOL_GPL(i3c_mctp_packet_alloc); + +/** + * i3c_mctp_packet_free() - frees i3c_mctp_packet + * + * @packet: pointer to the packet which should be freed + * + * Frees i3c_mctp_packet previously allocated via slab allocation + */ +void i3c_mctp_packet_free(void *packet) +{ + kmem_cache_free(packet_cache, packet); +} +EXPORT_SYMBOL_GPL(i3c_mctp_packet_free); + +static void i3c_mctp_client_free(struct i3c_mctp_client *client) +{ + ptr_ring_cleanup(&client->rx_queue, &i3c_mctp_packet_free); + + kfree(client); +} + +static struct i3c_mctp_client *i3c_mctp_client_alloc(struct i3c_mctp *priv) +{ + struct i3c_mctp_client *client; + int ret; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + goto out; + + client->priv = priv; + ret = ptr_ring_init(&client->rx_queue, RX_RING_COUNT, GFP_KERNEL); + if (ret) + return ERR_PTR(ret); + init_waitqueue_head(&client->wait_queue); +out: + return client; +} + +static struct i3c_mctp_client *i3c_mctp_find_client(struct i3c_mctp *priv, + struct i3c_mctp_packet *packet) +{ + return priv->default_client; +} + +static struct i3c_mctp_packet *i3c_mctp_read_packet(struct i3c_device *i3c) +{ + struct i3c_mctp_packet *rx_packet; + struct i3c_priv_xfer xfers = { + .rnw = true, + }; + int ret; + + rx_packet = i3c_mctp_packet_alloc(GFP_KERNEL); + if (!rx_packet) + return ERR_PTR(-ENOMEM); + + rx_packet->size = I3C_MCTP_PACKET_SIZE; + xfers.len = rx_packet->size; + xfers.data.in = &rx_packet->data; + + ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); + if (ret) { + i3c_mctp_packet_free(rx_packet); + return ERR_PTR(ret); + } + + return rx_packet; +} + +static void i3c_mctp_dispatch_packet(struct i3c_mctp *priv, struct i3c_mctp_packet *packet) +{ + struct i3c_mctp_client *client = i3c_mctp_find_client(priv, packet); + int ret; + + ret = ptr_ring_produce(&client->rx_queue, packet); + if (ret) + i3c_mctp_packet_free(packet); + else + wake_up_all(&client->wait_queue); +} + 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_mctp_packet *rx_packet; struct i3c_device_info info; int ret; @@ -57,12 +161,12 @@ static void i3c_mctp_polling_work(struct work_struct *work) 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); + rx_packet = i3c_mctp_read_packet(i3cdev); + if (IS_ERR(rx_packet)) + goto out; + i3c_mctp_dispatch_packet(priv, rx_packet); +out: schedule_delayed_work(&priv->polling_work, msecs_to_jiffies(POLLING_TIMEOUT_MS)); } @@ -92,40 +196,19 @@ static ssize_t i3c_mctp_write(struct file *file, const char __user *buf, size_t 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; + struct i3c_mctp_client *client = priv->default_client; + struct i3c_mctp_packet *rx_packet; - xfers.data.in = data; - - if (!priv->transfer_rdy) { - ret = -EFAULT; - goto out; - } + rx_packet = ptr_ring_consume(&client->rx_queue); + if (!rx_packet) + return -EAGAIN; - ret = i3c_device_do_priv_xfers(i3c, &xfers, 1); - if (ret) - goto out; + if (copy_to_user(buf, &rx_packet->data, count)) + return -EFAULT; - 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); + i3c_mctp_packet_free(rx_packet); -out: - kfree(data); - return ret ?: xfers.len; + return count; } static int i3c_mctp_open(struct inode *inode, struct file *file) @@ -141,7 +224,6 @@ static int i3c_mctp_open(struct inode *inode, struct file *file) spin_unlock(&priv->device_file_lock); file->private_data = priv; - init_waitqueue_head(priv->wait_queue); return 0; } @@ -164,9 +246,12 @@ 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); + poll_wait(file, &priv->default_client->wait_queue, pt); + + if (__ptr_ring_peek(&priv->default_client->rx_queue)) + ret |= EPOLLIN; - return priv->transfer_rdy ? EPOLLIN : ret; + return ret; } static const struct file_operations i3c_mctp_fops = { @@ -195,14 +280,8 @@ static struct i3c_mctp *i3c_mctp_alloc(struct i3c_device *i3c) 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; } @@ -210,24 +289,27 @@ static struct i3c_mctp *i3c_mctp_alloc(struct i3c_device *i3c) 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)); + struct i3c_mctp_packet *rx_packet; - spin_lock(&priv->device_file_lock); - if (!priv->device_open) { - spin_unlock(&priv->device_file_lock); + rx_packet = i3c_mctp_read_packet(dev); + if (IS_ERR(rx_packet)) 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); + i3c_mctp_dispatch_packet(priv, rx_packet); } static int i3c_mctp_init(struct i3c_driver *drv) { int ret; + packet_cache = kmem_cache_create_usercopy("mctp-i3c-packet", + sizeof(struct i3c_mctp_packet), 0, 0, 0, + sizeof(struct i3c_mctp_packet), NULL); + if (IS_ERR(packet_cache)) { + ret = PTR_ERR(packet_cache); + goto out; + } + /* Dynamically request unused major number */ ret = alloc_chrdev_region(&i3c_mctp_devt, 0, I3C_MCTP_MINORS, "i3c-mctp"); if (ret) @@ -256,6 +338,7 @@ 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); + kmem_cache_destroy(packet_cache); } static int i3c_mctp_enable_ibi(struct i3c_device *i3cdev) @@ -277,6 +360,76 @@ static int i3c_mctp_enable_ibi(struct i3c_device *i3cdev) return ret; } +/** + * i3c_mctp_get_eid() - receive MCTP EID assigned to the device + * + * @client: client for the device to get the EID for + * @domain_id: requested domain ID + * @eid: pointer to store EID value + * + * Receive MCTP endpoint ID dynamically assigned by the MCTP Bus Owner + * Return: 0 in case of success, a negative error code otherwise. + */ +int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid) +{ + /* TODO: Implement EID assignment basing on domain ID */ + *eid = 1; + return 0; +} +EXPORT_SYMBOL_GPL(i3c_mctp_get_eid); + +/** + * i3c_mctp_send_packet() - send mctp packet + * + * @tx_packet: the allocated packet that needs to be send via I3C + * @i3c: i3c device to send the packet to + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet) +{ + struct i3c_priv_xfer xfers = { + .rnw = false, + .len = tx_packet->size, + .data.out = &tx_packet->data, + }; + + return i3c_device_do_priv_xfers(i3c, &xfers, 1); +} +EXPORT_SYMBOL_GPL(i3c_mctp_send_packet); + +/** + * i3c_mctp_receive_packet() - receive mctp packet + * + * @client: i3c_mctp_client to receive the packet from + * @timeout: timeout, in jiffies + * + * The function will sleep for up to @timeout if no packet is ready to read. + * + * Returns struct i3c_mctp_packet from or ERR_PTR in case of error or the + * timeout elapsed. + */ +struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client, + unsigned long timeout) +{ + struct i3c_mctp_packet *rx_packet; + int ret; + + ret = wait_event_interruptible_timeout(client->wait_queue, + __ptr_ring_peek(&client->rx_queue), timeout); + if (ret < 0) + return ERR_PTR(ret); + else if (ret == 0) + return ERR_PTR(-ETIME); + + rx_packet = ptr_ring_consume(&client->rx_queue); + if (!rx_packet) + return ERR_PTR(-EAGAIN); + + return rx_packet; +} +EXPORT_SYMBOL_GPL(i3c_mctp_receive_packet); + static int i3c_mctp_probe(struct i3c_device *i3cdev) { struct i3c_mctp *priv; @@ -307,6 +460,10 @@ static int i3c_mctp_probe(struct i3c_device *i3cdev) if (ret) goto error; + priv->default_client = i3c_mctp_client_alloc(priv); + if (IS_ERR(priv->default_client)) + goto error; + dev_set_drvdata(i3cdev_to_dev(i3cdev), priv); if (i3c_mctp_enable_ibi(i3cdev)) { @@ -329,6 +486,8 @@ static void i3c_mctp_remove(struct i3c_device *i3cdev) i3c_device_disable_ibi(i3cdev); i3c_device_free_ibi(i3cdev); + i3c_mctp_client_free(priv->default_client); + priv->default_client = NULL; device_destroy(i3c_mctp_class, MKDEV(MAJOR(i3c_mctp_devt), priv->id)); cdev_del(&priv->cdev); diff --git a/include/linux/i3c/mctp/i3c-mctp.h b/include/linux/i3c/mctp/i3c-mctp.h new file mode 100644 index 000000000000..72f6fae922b5 --- /dev/null +++ b/include/linux/i3c/mctp/i3c-mctp.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2022 Intel Corporation.*/ + +#ifndef I3C_MCTP_H +#define I3C_MCTP_H + +#define I3C_MCTP_PACKET_SIZE 68 +#define I3C_MCTP_PAYLOAD_SIZE 64 +#define I3C_MCTP_HDR_SIZE 4 + +struct i3c_mctp_client; + +struct mctp_protocol_hdr { + u8 ver; + u8 dest; + u8 src; + u8 flags_seq_tag; +} __packed; + +struct i3c_mctp_packet_data { + u8 protocol_hdr[I3C_MCTP_HDR_SIZE]; + u8 payload[I3C_MCTP_PAYLOAD_SIZE]; +}; + +struct i3c_mctp_packet { + struct i3c_mctp_packet_data data; + u32 size; +}; + +void *i3c_mctp_packet_alloc(gfp_t flags); +void i3c_mctp_packet_free(void *packet); + +int i3c_mctp_get_eid(struct i3c_mctp_client *client, u8 domain_id, u8 *eid); +int i3c_mctp_send_packet(struct i3c_device *i3c, struct i3c_mctp_packet *tx_packet); +struct i3c_mctp_packet *i3c_mctp_receive_packet(struct i3c_mctp_client *client, + unsigned long timeout); + +#endif /* I3C_MCTP_H */ -- cgit v1.2.3