summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorOleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>2022-05-10 18:27:02 +0300
committerOleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>2022-06-01 11:46:09 +0300
commit426a368a8790b0561ef81bbd076fcc9c5e7e5ac1 (patch)
tree3dd348a287933ce8e2014dc584af90fc877421ea /drivers
parent1dbcc6ae7d8642d7835bf6a411060e0b9677c905 (diff)
downloadlinux-426a368a8790b0561ef81bbd076fcc9c5e7e5ac1.tar.xz
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 <oleksandr.shulzhenko.viktorovych@intel.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i3c/mctp/i3c-mctp.c279
1 files changed, 219 insertions, 60 deletions
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 <linux/module.h>
#include <linux/poll.h>
#include <linux/preempt.h>
+#include <linux/ptr_ring.h>
+#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/i3c/device.h>
+#include <linux/i3c/mctp/i3c-mctp.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
+#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);