2020-09-03 05:36:44

by Jie Deng

[permalink] [raw]
Subject: [PATCH] i2c: virtio: add a virtio i2c frontend driver

Add an I2C bus driver for virtio para-virtualization.

The controller can be emulated by the backend driver in
any device model software by following the virtio protocol.

This driver communicates with the backend driver through a
virtio I2C message structure which includes following parts:

- Header: i2c_msg addr, flags, len.
- Data buffer: the pointer to the i2c msg data.
- Status: the processing result from the backend.

People may implement different backend drivers to emulate
different controllers according to their needs. A backend
example can be found in the device model of the open source
project ACRN. For more information, please refer to
https://projectacrn.org.

The virtio device ID 34 is used for this I2C adpter since IDs
before 34 have been reserved by other virtio devices.

Co-developed-by: Conghui Chen <[email protected]>
Signed-off-by: Conghui Chen <[email protected]>
Signed-off-by: Jie Deng <[email protected]>
Reviewed-by: Shuo Liu <[email protected]>
Reviewed-by: Andy Shevchenko <[email protected]>
---
drivers/i2c/busses/Kconfig | 11 ++
drivers/i2c/busses/Makefile | 3 +
drivers/i2c/busses/i2c-virtio.c | 276 ++++++++++++++++++++++++++++++++++++++++
include/uapi/linux/virtio_ids.h | 1 +
4 files changed, 291 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-virtio.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 293e7a0..70c8e30 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -21,6 +21,17 @@ config I2C_ALI1535
This driver can also be built as a module. If so, the module
will be called i2c-ali1535.

+config I2C_VIRTIO
+ tristate "Virtio I2C Adapter"
+ depends on VIRTIO
+ help
+ If you say yes to this option, support will be included for the virtio
+ i2c adapter driver. The hardware can be emulated by any device model
+ software according to the virtio protocol.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-virtio.
+
config I2C_ALI1563
tristate "ALI 1563"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 19aff0e..821acfa 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -6,6 +6,9 @@
# ACPI drivers
obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o

+# VIRTIO I2C host controller driver
+obj-$(CONFIG_I2C_VIRTIO) += i2c-virtio.o
+
# PC SMBus host controller drivers
obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o
diff --git a/drivers/i2c/busses/i2c-virtio.c b/drivers/i2c/busses/i2c-virtio.c
new file mode 100644
index 0000000..47f9fd1
--- /dev/null
+++ b/drivers/i2c/busses/i2c-virtio.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Virtio I2C Bus Driver
+ *
+ * Copyright (c) 2020 Intel Corporation. All rights reserved.
+ */
+
+#include <linux/acpi.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+
+#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_config.h>
+
+#define VIRTIO_I2C_MSG_OK 0
+#define VIRTIO_I2C_MSG_ERR 1
+
+/**
+ * struct virtio_i2c_hdr - the virtio I2C message header structure
+ * @addr: i2c_msg addr, the slave address
+ * @flags: i2c_msg flags
+ * @len: i2c_msg len
+ */
+struct virtio_i2c_hdr {
+ __virtio16 addr;
+ __virtio16 flags;
+ __virtio16 len;
+} __packed;
+
+/**
+ * struct virtio_i2c_msg - the virtio I2C message structure
+ * @hdr: the virtio I2C message header
+ * @buf: virtio I2C message data buffer
+ * @status: the processing result from the backend
+ */
+struct virtio_i2c_msg {
+ struct virtio_i2c_hdr hdr;
+ char *buf;
+ u8 status;
+};
+
+/**
+ * struct virtio_i2c - virtio I2C data
+ * @vdev: virtio device for this controller
+ * @completion: completion of virtio I2C message
+ * @adap: I2C adapter for this controller
+ * @i2c_lock: lock for virtqueue processing
+ * @vq: the virtio virtqueue for communication
+ */
+struct virtio_i2c {
+ struct virtio_device *vdev;
+ struct completion completion;
+ struct i2c_adapter adap;
+ struct mutex i2c_lock;
+ struct virtqueue *vq;
+};
+
+static void virtio_i2c_msg_done(struct virtqueue *vq)
+{
+ struct virtio_i2c *vi = vq->vdev->priv;
+
+ complete(&vi->completion);
+}
+
+static int virtio_i2c_add_msg(struct virtqueue *vq,
+ struct virtio_i2c_msg *vmsg,
+ struct i2c_msg *msg)
+{
+ struct scatterlist *sgs[3], hdr, bout, bin, status;
+ int outcnt = 0, incnt = 0;
+
+ if (!msg->len)
+ return -EINVAL;
+
+ vmsg->hdr.addr = msg->addr;
+ vmsg->hdr.flags = msg->flags;
+ vmsg->hdr.len = msg->len;
+
+ vmsg->buf = kzalloc(vmsg->hdr.len, GFP_KERNEL);
+ if (!vmsg->buf)
+ return -ENOMEM;
+
+ sg_init_one(&hdr, &vmsg->hdr, sizeof(struct virtio_i2c_hdr));
+ sgs[outcnt++] = &hdr;
+ if (vmsg->hdr.flags & I2C_M_RD) {
+ sg_init_one(&bin, vmsg->buf, msg->len);
+ sgs[outcnt + incnt++] = &bin;
+ } else {
+ memcpy(vmsg->buf, msg->buf, msg->len);
+ sg_init_one(&bout, vmsg->buf, msg->len);
+ sgs[outcnt++] = &bout;
+ }
+ sg_init_one(&status, &vmsg->status, sizeof(vmsg->status));
+ sgs[outcnt + incnt++] = &status;
+
+ return virtqueue_add_sgs(vq, sgs, outcnt, incnt, vmsg, GFP_KERNEL);
+}
+
+static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct virtio_i2c *vi = i2c_get_adapdata(adap);
+ struct virtio_i2c_msg *vmsg_o, *vmsg_i;
+ struct virtqueue *vq = vi->vq;
+ unsigned long time_left;
+ int len, i, ret = 0;
+
+ vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
+ if (!vmsg_o)
+ return -ENOMEM;
+
+ mutex_lock(&vi->i2c_lock);
+ vmsg_o->buf = NULL;
+ for (i = 0; i < num; i++) {
+ ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
+ if (ret) {
+ dev_err(&adap->dev, "failed to add msg[%d] to virtqueue.\n", i);
+ goto err_unlock_free;
+ }
+
+ virtqueue_kick(vq);
+
+ time_left = wait_for_completion_timeout(&vi->completion, adap->timeout);
+ if (!time_left) {
+ dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i, msgs[i].addr);
+ ret = i;
+ goto err_unlock_free;
+ }
+
+ vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
+ if (vmsg_i) {
+ /* vmsg_i should point to the same address with vmsg_o */
+ if (vmsg_i != vmsg_o) {
+ dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue error.\n",
+ i, vmsg_i->hdr.addr);
+ ret = i;
+ goto err_unlock_free;
+ }
+ if (vmsg_i->status != VIRTIO_I2C_MSG_OK) {
+ dev_err(&adap->dev, "msg[%d]: addr=0x%x error=%d.\n",
+ i, vmsg_i->hdr.addr, vmsg_i->status);
+ ret = i;
+ goto err_unlock_free;
+ }
+ if ((vmsg_i->hdr.flags & I2C_M_RD) && vmsg_i->hdr.len)
+ memcpy(msgs[i].buf, vmsg_i->buf, vmsg_i->hdr.len);
+
+ kfree(vmsg_i->buf);
+ vmsg_i->buf = NULL;
+ }
+ reinit_completion(&vi->completion);
+ }
+ if (i == num)
+ ret = num;
+
+err_unlock_free:
+ mutex_unlock(&vi->i2c_lock);
+ kfree(vmsg_o->buf);
+ kfree(vmsg_o);
+ return ret;
+}
+
+static void virtio_i2c_del_vqs(struct virtio_device *vdev)
+{
+ vdev->config->reset(vdev);
+ vdev->config->del_vqs(vdev);
+}
+
+static int virtio_i2c_setup_vqs(struct virtio_i2c *vi)
+{
+ struct virtio_device *vdev = vi->vdev;
+
+ vi->vq = virtio_find_single_vq(vdev, virtio_i2c_msg_done, "i2c-msg");
+ return PTR_ERR_OR_ZERO(vi->vq);
+}
+
+static u32 virtio_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm virtio_algorithm = {
+ .master_xfer = virtio_i2c_xfer,
+ .functionality = virtio_i2c_func,
+};
+
+static struct i2c_adapter virtio_adapter = {
+ .owner = THIS_MODULE,
+ .name = "Virtio I2C Adapter",
+ .class = I2C_CLASS_DEPRECATED,
+ .algo = &virtio_algorithm,
+};
+
+static int virtio_i2c_probe(struct virtio_device *vdev)
+{
+ struct device *pdev = vdev->dev.parent;
+ struct virtio_i2c *vi;
+ int ret;
+
+ vi = devm_kzalloc(&vdev->dev, sizeof(*vi), GFP_KERNEL);
+ if (!vi)
+ return -ENOMEM;
+
+ vdev->priv = vi;
+ vi->vdev = vdev;
+
+ mutex_init(&vi->i2c_lock);
+ init_completion(&vi->completion);
+
+ ret = virtio_i2c_setup_vqs(vi);
+ if (ret)
+ return ret;
+
+ vi->adap = virtio_adapter;
+ i2c_set_adapdata(&vi->adap, vi);
+ vi->adap.dev.parent = &vdev->dev;
+ /* Setup ACPI node for slave devices which will be probed through ACPI */
+ ACPI_COMPANION_SET(&vi->adap.dev, ACPI_COMPANION(pdev));
+ vi->adap.timeout = HZ / 10;
+ ret = i2c_add_adapter(&vi->adap);
+ if (ret) {
+ dev_err(&vdev->dev, "failed to add virtio-i2c adapter.\n");
+ virtio_i2c_del_vqs(vdev);
+ }
+
+ return ret;
+}
+
+static void virtio_i2c_remove(struct virtio_device *vdev)
+{
+ struct virtio_i2c *vi = vdev->priv;
+
+ i2c_del_adapter(&vi->adap);
+ virtio_i2c_del_vqs(vdev);
+}
+
+static struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_I2C_ADPTER, VIRTIO_DEV_ANY_ID },
+ {}
+};
+MODULE_DEVICE_TABLE(virtio, id_table);
+
+static int __maybe_unused virtio_i2c_freeze(struct virtio_device *vdev)
+{
+ virtio_i2c_del_vqs(vdev);
+ return 0;
+}
+
+static int __maybe_unused virtio_i2c_restore(struct virtio_device *vdev)
+{
+ return virtio_i2c_setup_vqs(vdev->priv);
+}
+
+static struct virtio_driver virtio_i2c_driver = {
+ .id_table = id_table,
+ .probe = virtio_i2c_probe,
+ .remove = virtio_i2c_remove,
+ .driver = {
+ .name = "i2c_virtio",
+ },
+#ifdef CONFIG_PM_SLEEP
+ .freeze = virtio_i2c_freeze,
+ .restore = virtio_i2c_restore,
+#endif
+};
+module_virtio_driver(virtio_i2c_driver);
+
+MODULE_DESCRIPTION("Virtio i2c bus driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
index b052355..398ef2d 100644
--- a/include/uapi/linux/virtio_ids.h
+++ b/include/uapi/linux/virtio_ids.h
@@ -48,5 +48,6 @@
#define VIRTIO_ID_FS 26 /* virtio filesystem */
#define VIRTIO_ID_PMEM 27 /* virtio pmem */
#define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
+#define VIRTIO_ID_I2C_ADPTER 34 /* virtio i2c adpter */

#endif /* _LINUX_VIRTIO_IDS_H */
--
2.7.4


2020-09-03 06:14:14

by Jason Wang

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 下午1:34, Jie Deng wrote:
> Add an I2C bus driver for virtio para-virtualization.
>
> The controller can be emulated by the backend driver in
> any device model software by following the virtio protocol.
>
> This driver communicates with the backend driver through a
> virtio I2C message structure which includes following parts:
>
> - Header: i2c_msg addr, flags, len.
> - Data buffer: the pointer to the i2c msg data.
> - Status: the processing result from the backend.
>
> People may implement different backend drivers to emulate
> different controllers according to their needs. A backend
> example can be found in the device model of the open source
> project ACRN. For more information, please refer to
> https://projectacrn.org.


May I know the reason why don't you use i2c or virtio directly?


>
> The virtio device ID 34 is used for this I2C adpter since IDs
> before 34 have been reserved by other virtio devices.


Is there a link to the spec patch?

Thanks


>
> Co-developed-by: Conghui Chen <[email protected]>
> Signed-off-by: Conghui Chen <[email protected]>
> Signed-off-by: Jie Deng <[email protected]>
> Reviewed-by: Shuo Liu <[email protected]>
> Reviewed-by: Andy Shevchenko <[email protected]>
> ---
> drivers/i2c/busses/Kconfig | 11 ++
> drivers/i2c/busses/Makefile | 3 +
> drivers/i2c/busses/i2c-virtio.c | 276 ++++++++++++++++++++++++++++++++++++++++
> include/uapi/linux/virtio_ids.h | 1 +
> 4 files changed, 291 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-virtio.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 293e7a0..70c8e30 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -21,6 +21,17 @@ config I2C_ALI1535
> This driver can also be built as a module. If so, the module
> will be called i2c-ali1535.
>
> +config I2C_VIRTIO
> + tristate "Virtio I2C Adapter"
> + depends on VIRTIO
> + help
> + If you say yes to this option, support will be included for the virtio
> + i2c adapter driver. The hardware can be emulated by any device model
> + software according to the virtio protocol.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-virtio.
> +
> config I2C_ALI1563
> tristate "ALI 1563"
> depends on PCI
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 19aff0e..821acfa 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -6,6 +6,9 @@
> # ACPI drivers
> obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o
>
> +# VIRTIO I2C host controller driver
> +obj-$(CONFIG_I2C_VIRTIO) += i2c-virtio.o
> +
> # PC SMBus host controller drivers
> obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
> obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o
> diff --git a/drivers/i2c/busses/i2c-virtio.c b/drivers/i2c/busses/i2c-virtio.c
> new file mode 100644
> index 0000000..47f9fd1
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-virtio.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Virtio I2C Bus Driver
> + *
> + * Copyright (c) 2020 Intel Corporation. All rights reserved.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/io.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/wait.h>
> +
> +#include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_config.h>
> +
> +#define VIRTIO_I2C_MSG_OK 0
> +#define VIRTIO_I2C_MSG_ERR 1
> +
> +/**
> + * struct virtio_i2c_hdr - the virtio I2C message header structure
> + * @addr: i2c_msg addr, the slave address
> + * @flags: i2c_msg flags
> + * @len: i2c_msg len
> + */
> +struct virtio_i2c_hdr {
> + __virtio16 addr;
> + __virtio16 flags;
> + __virtio16 len;
> +} __packed;
> +
> +/**
> + * struct virtio_i2c_msg - the virtio I2C message structure
> + * @hdr: the virtio I2C message header
> + * @buf: virtio I2C message data buffer
> + * @status: the processing result from the backend
> + */
> +struct virtio_i2c_msg {
> + struct virtio_i2c_hdr hdr;
> + char *buf;
> + u8 status;
> +};
> +
> +/**
> + * struct virtio_i2c - virtio I2C data
> + * @vdev: virtio device for this controller
> + * @completion: completion of virtio I2C message
> + * @adap: I2C adapter for this controller
> + * @i2c_lock: lock for virtqueue processing
> + * @vq: the virtio virtqueue for communication
> + */
> +struct virtio_i2c {
> + struct virtio_device *vdev;
> + struct completion completion;
> + struct i2c_adapter adap;
> + struct mutex i2c_lock;
> + struct virtqueue *vq;
> +};
> +
> +static void virtio_i2c_msg_done(struct virtqueue *vq)
> +{
> + struct virtio_i2c *vi = vq->vdev->priv;
> +
> + complete(&vi->completion);
> +}
> +
> +static int virtio_i2c_add_msg(struct virtqueue *vq,
> + struct virtio_i2c_msg *vmsg,
> + struct i2c_msg *msg)
> +{
> + struct scatterlist *sgs[3], hdr, bout, bin, status;
> + int outcnt = 0, incnt = 0;
> +
> + if (!msg->len)
> + return -EINVAL;
> +
> + vmsg->hdr.addr = msg->addr;
> + vmsg->hdr.flags = msg->flags;
> + vmsg->hdr.len = msg->len;
> +
> + vmsg->buf = kzalloc(vmsg->hdr.len, GFP_KERNEL);
> + if (!vmsg->buf)
> + return -ENOMEM;
> +
> + sg_init_one(&hdr, &vmsg->hdr, sizeof(struct virtio_i2c_hdr));
> + sgs[outcnt++] = &hdr;
> + if (vmsg->hdr.flags & I2C_M_RD) {
> + sg_init_one(&bin, vmsg->buf, msg->len);
> + sgs[outcnt + incnt++] = &bin;
> + } else {
> + memcpy(vmsg->buf, msg->buf, msg->len);
> + sg_init_one(&bout, vmsg->buf, msg->len);
> + sgs[outcnt++] = &bout;
> + }
> + sg_init_one(&status, &vmsg->status, sizeof(vmsg->status));
> + sgs[outcnt + incnt++] = &status;
> +
> + return virtqueue_add_sgs(vq, sgs, outcnt, incnt, vmsg, GFP_KERNEL);
> +}
> +
> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
> + struct virtio_i2c *vi = i2c_get_adapdata(adap);
> + struct virtio_i2c_msg *vmsg_o, *vmsg_i;
> + struct virtqueue *vq = vi->vq;
> + unsigned long time_left;
> + int len, i, ret = 0;
> +
> + vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
> + if (!vmsg_o)
> + return -ENOMEM;
> +
> + mutex_lock(&vi->i2c_lock);
> + vmsg_o->buf = NULL;
> + for (i = 0; i < num; i++) {
> + ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
> + if (ret) {
> + dev_err(&adap->dev, "failed to add msg[%d] to virtqueue.\n", i);
> + goto err_unlock_free;
> + }
> +
> + virtqueue_kick(vq);
> +
> + time_left = wait_for_completion_timeout(&vi->completion, adap->timeout);
> + if (!time_left) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i, msgs[i].addr);
> + ret = i;
> + goto err_unlock_free;
> + }
> +
> + vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
> + if (vmsg_i) {
> + /* vmsg_i should point to the same address with vmsg_o */
> + if (vmsg_i != vmsg_o) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue error.\n",
> + i, vmsg_i->hdr.addr);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if (vmsg_i->status != VIRTIO_I2C_MSG_OK) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x error=%d.\n",
> + i, vmsg_i->hdr.addr, vmsg_i->status);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if ((vmsg_i->hdr.flags & I2C_M_RD) && vmsg_i->hdr.len)
> + memcpy(msgs[i].buf, vmsg_i->buf, vmsg_i->hdr.len);
> +
> + kfree(vmsg_i->buf);
> + vmsg_i->buf = NULL;
> + }
> + reinit_completion(&vi->completion);
> + }
> + if (i == num)
> + ret = num;
> +
> +err_unlock_free:
> + mutex_unlock(&vi->i2c_lock);
> + kfree(vmsg_o->buf);
> + kfree(vmsg_o);
> + return ret;
> +}
> +
> +static void virtio_i2c_del_vqs(struct virtio_device *vdev)
> +{
> + vdev->config->reset(vdev);
> + vdev->config->del_vqs(vdev);
> +}
> +
> +static int virtio_i2c_setup_vqs(struct virtio_i2c *vi)
> +{
> + struct virtio_device *vdev = vi->vdev;
> +
> + vi->vq = virtio_find_single_vq(vdev, virtio_i2c_msg_done, "i2c-msg");
> + return PTR_ERR_OR_ZERO(vi->vq);
> +}
> +
> +static u32 virtio_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static struct i2c_algorithm virtio_algorithm = {
> + .master_xfer = virtio_i2c_xfer,
> + .functionality = virtio_i2c_func,
> +};
> +
> +static struct i2c_adapter virtio_adapter = {
> + .owner = THIS_MODULE,
> + .name = "Virtio I2C Adapter",
> + .class = I2C_CLASS_DEPRECATED,
> + .algo = &virtio_algorithm,
> +};
> +
> +static int virtio_i2c_probe(struct virtio_device *vdev)
> +{
> + struct device *pdev = vdev->dev.parent;
> + struct virtio_i2c *vi;
> + int ret;
> +
> + vi = devm_kzalloc(&vdev->dev, sizeof(*vi), GFP_KERNEL);
> + if (!vi)
> + return -ENOMEM;
> +
> + vdev->priv = vi;
> + vi->vdev = vdev;
> +
> + mutex_init(&vi->i2c_lock);
> + init_completion(&vi->completion);
> +
> + ret = virtio_i2c_setup_vqs(vi);
> + if (ret)
> + return ret;
> +
> + vi->adap = virtio_adapter;
> + i2c_set_adapdata(&vi->adap, vi);
> + vi->adap.dev.parent = &vdev->dev;
> + /* Setup ACPI node for slave devices which will be probed through ACPI */
> + ACPI_COMPANION_SET(&vi->adap.dev, ACPI_COMPANION(pdev));
> + vi->adap.timeout = HZ / 10;
> + ret = i2c_add_adapter(&vi->adap);
> + if (ret) {
> + dev_err(&vdev->dev, "failed to add virtio-i2c adapter.\n");
> + virtio_i2c_del_vqs(vdev);
> + }
> +
> + return ret;
> +}
> +
> +static void virtio_i2c_remove(struct virtio_device *vdev)
> +{
> + struct virtio_i2c *vi = vdev->priv;
> +
> + i2c_del_adapter(&vi->adap);
> + virtio_i2c_del_vqs(vdev);
> +}
> +
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_I2C_ADPTER, VIRTIO_DEV_ANY_ID },
> + {}
> +};
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +
> +static int __maybe_unused virtio_i2c_freeze(struct virtio_device *vdev)
> +{
> + virtio_i2c_del_vqs(vdev);
> + return 0;
> +}
> +
> +static int __maybe_unused virtio_i2c_restore(struct virtio_device *vdev)
> +{
> + return virtio_i2c_setup_vqs(vdev->priv);
> +}
> +
> +static struct virtio_driver virtio_i2c_driver = {
> + .id_table = id_table,
> + .probe = virtio_i2c_probe,
> + .remove = virtio_i2c_remove,
> + .driver = {
> + .name = "i2c_virtio",
> + },
> +#ifdef CONFIG_PM_SLEEP
> + .freeze = virtio_i2c_freeze,
> + .restore = virtio_i2c_restore,
> +#endif
> +};
> +module_virtio_driver(virtio_i2c_driver);
> +
> +MODULE_DESCRIPTION("Virtio i2c bus driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index b052355..398ef2d 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -48,5 +48,6 @@
> #define VIRTIO_ID_FS 26 /* virtio filesystem */
> #define VIRTIO_ID_PMEM 27 /* virtio pmem */
> #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
> +#define VIRTIO_ID_I2C_ADPTER 34 /* virtio i2c adpter */
>
> #endif /* _LINUX_VIRTIO_IDS_H */

2020-09-03 07:23:30

by Jie Deng

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 14:12, Jason Wang wrote:
>
> On 2020/9/3 下午1:34, Jie Deng wrote:
>> Add an I2C bus driver for virtio para-virtualization.
>>
>> The controller can be emulated by the backend driver in
>> any device model software by following the virtio protocol.
>>
>> This driver communicates with the backend driver through a
>> virtio I2C message structure which includes following parts:
>>
>> - Header: i2c_msg addr, flags, len.
>> - Data buffer: the pointer to the i2c msg data.
>> - Status: the processing result from the backend.
>>
>> People may implement different backend drivers to emulate
>> different controllers according to their needs. A backend
>> example can be found in the device model of the open source
>> project ACRN. For more information, please refer to
>> https://projectacrn.org.
>
>
> May I know the reason why don't you use i2c or virtio directly?
>
We don't want to add virtio drivers for every I2C devices in the guests.
This bus driver is designed to provide a way to flexibly expose the
physical
I2C slave devices to the guest without adding or changing the drivers of
the
I2C slave devices in the guest OS.


>
>>
>> The virtio device ID 34 is used for this I2C adpter since IDs
>> before 34 have been reserved by other virtio devices.
>
>
> Is there a link to the spec patch?
>
> Thanks
>
I haven't submitted the patch to reserve the ID in spec yet.
I write the ID here because I want to see your opinions first.

Thanks


2020-09-03 09:11:31

by Loic Poulain

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver

On Thu, 3 Sep 2020 at 09:19, Jie Deng <[email protected]> wrote:
>
>
> On 2020/9/3 14:12, Jason Wang wrote:
> >
> > On 2020/9/3 下午1:34, Jie Deng wrote:
> >> Add an I2C bus driver for virtio para-virtualization.
> >>
> >> The controller can be emulated by the backend driver in
> >> any device model software by following the virtio protocol.
> >>
> >> This driver communicates with the backend driver through a
> >> virtio I2C message structure which includes following parts:
> >>
> >> - Header: i2c_msg addr, flags, len.
> >> - Data buffer: the pointer to the i2c msg data.
> >> - Status: the processing result from the backend.
> >>
> >> People may implement different backend drivers to emulate
> >> different controllers according to their needs. A backend
> >> example can be found in the device model of the open source
> >> project ACRN. For more information, please refer to
> >> https://projectacrn.org.
> >
> >
> > May I know the reason why don't you use i2c or virtio directly?
> >
> We don't want to add virtio drivers for every I2C devices in the guests.
> This bus driver is designed to provide a way to flexibly expose the
> physical
> I2C slave devices to the guest without adding or changing the drivers of
> the
> I2C slave devices in the guest OS.

So AFAIU, what you're trying to do here is "I2C slave passthrough over
para-virtualized I2C bus"? While not totally crazy, that looks a bit weird since
a straightforward way would be to directly assign the I2C bus controller as a
passthrough device (vfio?), though I assume your goal is also having per slave
VM assignment control (and not exposing the whole bus)...

>
>
> >
> >>
> >> The virtio device ID 34 is used for this I2C adpter since IDs
> >> before 34 have been reserved by other virtio devices.
> >
> >
> > Is there a link to the spec patch?
> >
> > Thanks
> >
> I haven't submitted the patch to reserve the ID in spec yet.
> I write the ID here because I want to see your opinions first.
>
> Thanks
>
>

Regards,
Loic

2020-09-03 10:02:14

by Michael S. Tsirkin

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver

On Thu, Sep 03, 2020 at 01:34:45PM +0800, Jie Deng wrote:
> Add an I2C bus driver for virtio para-virtualization.
>
> The controller can be emulated by the backend driver in
> any device model software by following the virtio protocol.
>
> This driver communicates with the backend driver through a
> virtio I2C message structure which includes following parts:
>
> - Header: i2c_msg addr, flags, len.
> - Data buffer: the pointer to the i2c msg data.
> - Status: the processing result from the backend.
>
> People may implement different backend drivers to emulate
> different controllers according to their needs. A backend
> example can be found in the device model of the open source
> project ACRN. For more information, please refer to
> https://projectacrn.org.
>
> The virtio device ID 34 is used for this I2C adpter since IDs
> before 34 have been reserved by other virtio devices.

Please reserve the ID with the virtio tc so no one conflicts.


> Co-developed-by: Conghui Chen <[email protected]>
> Signed-off-by: Conghui Chen <[email protected]>
> Signed-off-by: Jie Deng <[email protected]>
> Reviewed-by: Shuo Liu <[email protected]>
> Reviewed-by: Andy Shevchenko <[email protected]>
> ---
> drivers/i2c/busses/Kconfig | 11 ++
> drivers/i2c/busses/Makefile | 3 +
> drivers/i2c/busses/i2c-virtio.c | 276 ++++++++++++++++++++++++++++++++++++++++
> include/uapi/linux/virtio_ids.h | 1 +
> 4 files changed, 291 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-virtio.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 293e7a0..70c8e30 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -21,6 +21,17 @@ config I2C_ALI1535
> This driver can also be built as a module. If so, the module
> will be called i2c-ali1535.
>
> +config I2C_VIRTIO
> + tristate "Virtio I2C Adapter"
> + depends on VIRTIO
> + help
> + If you say yes to this option, support will be included for the virtio
> + i2c adapter driver. The hardware can be emulated by any device model
> + software according to the virtio protocol.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-virtio.
> +
> config I2C_ALI1563
> tristate "ALI 1563"
> depends on PCI
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 19aff0e..821acfa 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -6,6 +6,9 @@
> # ACPI drivers
> obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o
>
> +# VIRTIO I2C host controller driver
> +obj-$(CONFIG_I2C_VIRTIO) += i2c-virtio.o
> +
> # PC SMBus host controller drivers
> obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
> obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o
> diff --git a/drivers/i2c/busses/i2c-virtio.c b/drivers/i2c/busses/i2c-virtio.c
> new file mode 100644
> index 0000000..47f9fd1
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-virtio.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Virtio I2C Bus Driver
> + *
> + * Copyright (c) 2020 Intel Corporation. All rights reserved.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/io.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/wait.h>
> +
> +#include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_config.h>
> +
> +#define VIRTIO_I2C_MSG_OK 0
> +#define VIRTIO_I2C_MSG_ERR 1
> +
> +/**
> + * struct virtio_i2c_hdr - the virtio I2C message header structure
> + * @addr: i2c_msg addr, the slave address
> + * @flags: i2c_msg flags
> + * @len: i2c_msg len
> + */
> +struct virtio_i2c_hdr {
> + __virtio16 addr;
> + __virtio16 flags;
> + __virtio16 len;
> +} __packed;

virtio16 is for legacy devices, modern ones should be __le.
and we don't really need to pack it I think.

> +
> +/**
> + * struct virtio_i2c_msg - the virtio I2C message structure
> + * @hdr: the virtio I2C message header
> + * @buf: virtio I2C message data buffer
> + * @status: the processing result from the backend
> + */
> +struct virtio_i2c_msg {
> + struct virtio_i2c_hdr hdr;
> + char *buf;
> + u8 status;
> +};
> +
> +/**
> + * struct virtio_i2c - virtio I2C data
> + * @vdev: virtio device for this controller
> + * @completion: completion of virtio I2C message
> + * @adap: I2C adapter for this controller
> + * @i2c_lock: lock for virtqueue processing
> + * @vq: the virtio virtqueue for communication
> + */
> +struct virtio_i2c {
> + struct virtio_device *vdev;
> + struct completion completion;
> + struct i2c_adapter adap;
> + struct mutex i2c_lock;
> + struct virtqueue *vq;
> +};
> +
> +static void virtio_i2c_msg_done(struct virtqueue *vq)
> +{
> + struct virtio_i2c *vi = vq->vdev->priv;
> +
> + complete(&vi->completion);
> +}
> +
> +static int virtio_i2c_add_msg(struct virtqueue *vq,
> + struct virtio_i2c_msg *vmsg,
> + struct i2c_msg *msg)
> +{
> + struct scatterlist *sgs[3], hdr, bout, bin, status;
> + int outcnt = 0, incnt = 0;
> +
> + if (!msg->len)
> + return -EINVAL;
> +
> + vmsg->hdr.addr = msg->addr;
> + vmsg->hdr.flags = msg->flags;
> + vmsg->hdr.len = msg->len;
> +
> + vmsg->buf = kzalloc(vmsg->hdr.len, GFP_KERNEL);
> + if (!vmsg->buf)
> + return -ENOMEM;
> +
> + sg_init_one(&hdr, &vmsg->hdr, sizeof(struct virtio_i2c_hdr));
> + sgs[outcnt++] = &hdr;
> + if (vmsg->hdr.flags & I2C_M_RD) {
> + sg_init_one(&bin, vmsg->buf, msg->len);
> + sgs[outcnt + incnt++] = &bin;
> + } else {
> + memcpy(vmsg->buf, msg->buf, msg->len);
> + sg_init_one(&bout, vmsg->buf, msg->len);
> + sgs[outcnt++] = &bout;
> + }
> + sg_init_one(&status, &vmsg->status, sizeof(vmsg->status));
> + sgs[outcnt + incnt++] = &status;
> +
> + return virtqueue_add_sgs(vq, sgs, outcnt, incnt, vmsg, GFP_KERNEL);
> +}
> +
> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
> + struct virtio_i2c *vi = i2c_get_adapdata(adap);
> + struct virtio_i2c_msg *vmsg_o, *vmsg_i;
> + struct virtqueue *vq = vi->vq;
> + unsigned long time_left;
> + int len, i, ret = 0;
> +
> + vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
> + if (!vmsg_o)
> + return -ENOMEM;
> +
> + mutex_lock(&vi->i2c_lock);
> + vmsg_o->buf = NULL;
> + for (i = 0; i < num; i++) {
> + ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
> + if (ret) {
> + dev_err(&adap->dev, "failed to add msg[%d] to virtqueue.\n", i);
> + goto err_unlock_free;
> + }
> +
> + virtqueue_kick(vq);
> +
> + time_left = wait_for_completion_timeout(&vi->completion, adap->timeout);
> + if (!time_left) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i, msgs[i].addr);
> + ret = i;
> + goto err_unlock_free;
> + }
> +
> + vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
> + if (vmsg_i) {
> + /* vmsg_i should point to the same address with vmsg_o */
> + if (vmsg_i != vmsg_o) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue error.\n",
> + i, vmsg_i->hdr.addr);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if (vmsg_i->status != VIRTIO_I2C_MSG_OK) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x error=%d.\n",
> + i, vmsg_i->hdr.addr, vmsg_i->status);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if ((vmsg_i->hdr.flags & I2C_M_RD) && vmsg_i->hdr.len)
> + memcpy(msgs[i].buf, vmsg_i->buf, vmsg_i->hdr.len);
> +
> + kfree(vmsg_i->buf);
> + vmsg_i->buf = NULL;
> + }
> + reinit_completion(&vi->completion);
> + }
> + if (i == num)
> + ret = num;
> +
> +err_unlock_free:
> + mutex_unlock(&vi->i2c_lock);
> + kfree(vmsg_o->buf);
> + kfree(vmsg_o);
> + return ret;
> +}
> +
> +static void virtio_i2c_del_vqs(struct virtio_device *vdev)
> +{
> + vdev->config->reset(vdev);
> + vdev->config->del_vqs(vdev);
> +}
> +
> +static int virtio_i2c_setup_vqs(struct virtio_i2c *vi)
> +{
> + struct virtio_device *vdev = vi->vdev;
> +
> + vi->vq = virtio_find_single_vq(vdev, virtio_i2c_msg_done, "i2c-msg");
> + return PTR_ERR_OR_ZERO(vi->vq);
> +}
> +
> +static u32 virtio_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static struct i2c_algorithm virtio_algorithm = {
> + .master_xfer = virtio_i2c_xfer,
> + .functionality = virtio_i2c_func,
> +};
> +
> +static struct i2c_adapter virtio_adapter = {
> + .owner = THIS_MODULE,
> + .name = "Virtio I2C Adapter",
> + .class = I2C_CLASS_DEPRECATED,
> + .algo = &virtio_algorithm,
> +};
> +
> +static int virtio_i2c_probe(struct virtio_device *vdev)
> +{
> + struct device *pdev = vdev->dev.parent;
> + struct virtio_i2c *vi;
> + int ret;
> +
> + vi = devm_kzalloc(&vdev->dev, sizeof(*vi), GFP_KERNEL);
> + if (!vi)
> + return -ENOMEM;
> +
> + vdev->priv = vi;
> + vi->vdev = vdev;
> +
> + mutex_init(&vi->i2c_lock);
> + init_completion(&vi->completion);
> +
> + ret = virtio_i2c_setup_vqs(vi);
> + if (ret)
> + return ret;
> +
> + vi->adap = virtio_adapter;
> + i2c_set_adapdata(&vi->adap, vi);
> + vi->adap.dev.parent = &vdev->dev;
> + /* Setup ACPI node for slave devices which will be probed through ACPI */
> + ACPI_COMPANION_SET(&vi->adap.dev, ACPI_COMPANION(pdev));
> + vi->adap.timeout = HZ / 10;
> + ret = i2c_add_adapter(&vi->adap);
> + if (ret) {
> + dev_err(&vdev->dev, "failed to add virtio-i2c adapter.\n");
> + virtio_i2c_del_vqs(vdev);
> + }
> +
> + return ret;
> +}
> +
> +static void virtio_i2c_remove(struct virtio_device *vdev)
> +{
> + struct virtio_i2c *vi = vdev->priv;
> +
> + i2c_del_adapter(&vi->adap);
> + virtio_i2c_del_vqs(vdev);
> +}
> +
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_I2C_ADPTER, VIRTIO_DEV_ANY_ID },
> + {}
> +};
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +
> +static int __maybe_unused virtio_i2c_freeze(struct virtio_device *vdev)
> +{
> + virtio_i2c_del_vqs(vdev);
> + return 0;
> +}
> +
> +static int __maybe_unused virtio_i2c_restore(struct virtio_device *vdev)
> +{
> + return virtio_i2c_setup_vqs(vdev->priv);
> +}
> +
> +static struct virtio_driver virtio_i2c_driver = {
> + .id_table = id_table,
> + .probe = virtio_i2c_probe,
> + .remove = virtio_i2c_remove,
> + .driver = {
> + .name = "i2c_virtio",
> + },
> +#ifdef CONFIG_PM_SLEEP
> + .freeze = virtio_i2c_freeze,
> + .restore = virtio_i2c_restore,
> +#endif
> +};
> +module_virtio_driver(virtio_i2c_driver);
> +
> +MODULE_DESCRIPTION("Virtio i2c bus driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index b052355..398ef2d 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -48,5 +48,6 @@
> #define VIRTIO_ID_FS 26 /* virtio filesystem */
> #define VIRTIO_ID_PMEM 27 /* virtio pmem */
> #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
> +#define VIRTIO_ID_I2C_ADPTER 34 /* virtio i2c adpter */
>
> #endif /* _LINUX_VIRTIO_IDS_H */
> --
> 2.7.4

2020-09-03 10:21:34

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver

On Thu, Sep 03, 2020 at 01:34:45PM +0800, Jie Deng wrote:
> Add an I2C bus driver for virtio para-virtualization.
>
> The controller can be emulated by the backend driver in
> any device model software by following the virtio protocol.
>
> This driver communicates with the backend driver through a
> virtio I2C message structure which includes following parts:
>
> - Header: i2c_msg addr, flags, len.
> - Data buffer: the pointer to the i2c msg data.
> - Status: the processing result from the backend.
>
> People may implement different backend drivers to emulate
> different controllers according to their needs. A backend
> example can be found in the device model of the open source
> project ACRN. For more information, please refer to
> https://projectacrn.org.
>
> The virtio device ID 34 is used for this I2C adpter since IDs
> before 34 have been reserved by other virtio devices.

Seems it's slightly different version to what I have reviewed internally.
My comments below. (I admit that some of them maybe new)

...

> +/**
> + * struct virtio_i2c_hdr - the virtio I2C message header structure
> + * @addr: i2c_msg addr, the slave address
> + * @flags: i2c_msg flags
> + * @len: i2c_msg len
> + */
> +struct virtio_i2c_hdr {
> + __virtio16 addr;
> + __virtio16 flags;
> + __virtio16 len;
> +} __packed;

As Misha noticed and somewhere I saw 0-day reports these should be carefully
taken care of.

...

> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
> + struct virtio_i2c *vi = i2c_get_adapdata(adap);
> + struct virtio_i2c_msg *vmsg_o, *vmsg_i;
> + struct virtqueue *vq = vi->vq;
> + unsigned long time_left;
> + int len, i, ret = 0;
> +
> + vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
> + if (!vmsg_o)
> + return -ENOMEM;
> +
> + mutex_lock(&vi->i2c_lock);
> + vmsg_o->buf = NULL;
> + for (i = 0; i < num; i++) {
> + ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
> + if (ret) {
> + dev_err(&adap->dev, "failed to add msg[%d] to virtqueue.\n", i);

> + goto err_unlock_free;

break;

> + }
> +
> + virtqueue_kick(vq);
> +
> + time_left = wait_for_completion_timeout(&vi->completion, adap->timeout);
> + if (!time_left) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i, msgs[i].addr);
> + ret = i;

> + goto err_unlock_free;

break;

And so on.

> + }
> +
> + vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
> + if (vmsg_i) {
> + /* vmsg_i should point to the same address with vmsg_o */
> + if (vmsg_i != vmsg_o) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue error.\n",
> + i, vmsg_i->hdr.addr);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if (vmsg_i->status != VIRTIO_I2C_MSG_OK) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x error=%d.\n",
> + i, vmsg_i->hdr.addr, vmsg_i->status);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if ((vmsg_i->hdr.flags & I2C_M_RD) && vmsg_i->hdr.len)
> + memcpy(msgs[i].buf, vmsg_i->buf, vmsg_i->hdr.len);
> +
> + kfree(vmsg_i->buf);
> + vmsg_i->buf = NULL;
> + }
> + reinit_completion(&vi->completion);
> + }

> + if (i == num)
> + ret = num;

And this conditional seems a dup of the for-loop successfully iterating over
entire queue.

> +err_unlock_free:

Redundant.

> + mutex_unlock(&vi->i2c_lock);
> + kfree(vmsg_o->buf);
> + kfree(vmsg_o);
> + return ret;
> +}

...

> + vi->adap.timeout = HZ / 10;

+ Blank line.

> + ret = i2c_add_adapter(&vi->adap);
> + if (ret) {

> + dev_err(&vdev->dev, "failed to add virtio-i2c adapter.\n");
> + virtio_i2c_del_vqs(vdev);

Usually we do clean up followed by message.

> + }
> +
> + return ret;


--
With Best Regards,
Andy Shevchenko


2020-09-04 03:40:20

by Jason Wang

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 下午3:19, Jie Deng wrote:
>
> On 2020/9/3 14:12, Jason Wang wrote:
>>
>> On 2020/9/3 下午1:34, Jie Deng wrote:
>>> Add an I2C bus driver for virtio para-virtualization.
>>>
>>> The controller can be emulated by the backend driver in
>>> any device model software by following the virtio protocol.
>>>
>>> This driver communicates with the backend driver through a
>>> virtio I2C message structure which includes following parts:
>>>
>>> - Header: i2c_msg addr, flags, len.
>>> - Data buffer: the pointer to the i2c msg data.
>>> - Status: the processing result from the backend.
>>>
>>> People may implement different backend drivers to emulate
>>> different controllers according to their needs. A backend
>>> example can be found in the device model of the open source
>>> project ACRN. For more information, please refer to
>>> https://projectacrn.org.
>>
>>
>> May I know the reason why don't you use i2c or virtio directly?
>>
> We don't want to add virtio drivers for every I2C devices in the guests.
> This bus driver is designed to provide a way to flexibly expose the
> physical
> I2C slave devices to the guest without adding or changing the drivers
> of the
> I2C slave devices in the guest OS.


Ok, if I understand this correctly, this is virtio transport of i2c
message (similar to virtio-scsi).


>
>
>>
>>>
>>> The virtio device ID 34 is used for this I2C adpter since IDs
>>> before 34 have been reserved by other virtio devices.
>>
>>
>> Is there a link to the spec patch?
>>
>> Thanks
>>
> I haven't submitted the patch to reserve the ID in spec yet.
> I write the ID here because I want to see your opinions first.


It would be helpful to send a spec draft for early review.

Thanks


>
> Thanks
>
>

2020-09-04 04:08:15

by Jason Wang

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 下午1:34, Jie Deng wrote:
> Add an I2C bus driver for virtio para-virtualization.
>
> The controller can be emulated by the backend driver in
> any device model software by following the virtio protocol.
>
> This driver communicates with the backend driver through a
> virtio I2C message structure which includes following parts:
>
> - Header: i2c_msg addr, flags, len.
> - Data buffer: the pointer to the i2c msg data.
> - Status: the processing result from the backend.
>
> People may implement different backend drivers to emulate
> different controllers according to their needs. A backend
> example can be found in the device model of the open source
> project ACRN. For more information, please refer to
> https://projectacrn.org.
>
> The virtio device ID 34 is used for this I2C adpter since IDs
> before 34 have been reserved by other virtio devices.
>
> Co-developed-by: Conghui Chen <[email protected]>
> Signed-off-by: Conghui Chen <[email protected]>
> Signed-off-by: Jie Deng <[email protected]>
> Reviewed-by: Shuo Liu <[email protected]>
> Reviewed-by: Andy Shevchenko <[email protected]>
> ---
> drivers/i2c/busses/Kconfig | 11 ++
> drivers/i2c/busses/Makefile | 3 +
> drivers/i2c/busses/i2c-virtio.c | 276 ++++++++++++++++++++++++++++++++++++++++
> include/uapi/linux/virtio_ids.h | 1 +
> 4 files changed, 291 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-virtio.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 293e7a0..70c8e30 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -21,6 +21,17 @@ config I2C_ALI1535
> This driver can also be built as a module. If so, the module
> will be called i2c-ali1535.
>
> +config I2C_VIRTIO
> + tristate "Virtio I2C Adapter"
> + depends on VIRTIO


I guess it should depend on some I2C module here.


> + help
> + If you say yes to this option, support will be included for the virtio
> + i2c adapter driver. The hardware can be emulated by any device model
> + software according to the virtio protocol.
> +
> + This driver can also be built as a module. If so, the module
> + will be called i2c-virtio.
> +
> config I2C_ALI1563
> tristate "ALI 1563"
> depends on PCI
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 19aff0e..821acfa 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -6,6 +6,9 @@
> # ACPI drivers
> obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o
>
> +# VIRTIO I2C host controller driver
> +obj-$(CONFIG_I2C_VIRTIO) += i2c-virtio.o
> +
> # PC SMBus host controller drivers
> obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
> obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o
> diff --git a/drivers/i2c/busses/i2c-virtio.c b/drivers/i2c/busses/i2c-virtio.c
> new file mode 100644
> index 0000000..47f9fd1
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-virtio.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Virtio I2C Bus Driver
> + *
> + * Copyright (c) 2020 Intel Corporation. All rights reserved.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/io.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/wait.h>
> +
> +#include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_config.h>
> +
> +#define VIRTIO_I2C_MSG_OK 0
> +#define VIRTIO_I2C_MSG_ERR 1
> +
> +/**
> + * struct virtio_i2c_hdr - the virtio I2C message header structure
> + * @addr: i2c_msg addr, the slave address
> + * @flags: i2c_msg flags
> + * @len: i2c_msg len
> + */
> +struct virtio_i2c_hdr {
> + __virtio16 addr;
> + __virtio16 flags;
> + __virtio16 len;
> +} __packed;
> +
> +/**
> + * struct virtio_i2c_msg - the virtio I2C message structure
> + * @hdr: the virtio I2C message header
> + * @buf: virtio I2C message data buffer
> + * @status: the processing result from the backend
> + */
> +struct virtio_i2c_msg {
> + struct virtio_i2c_hdr hdr;
> + char *buf;
> + u8 status;


Any reason for separating status out of virtio_i2c_hdr?


> +};
> +
> +/**
> + * struct virtio_i2c - virtio I2C data
> + * @vdev: virtio device for this controller
> + * @completion: completion of virtio I2C message
> + * @adap: I2C adapter for this controller
> + * @i2c_lock: lock for virtqueue processing
> + * @vq: the virtio virtqueue for communication
> + */
> +struct virtio_i2c {
> + struct virtio_device *vdev;
> + struct completion completion;
> + struct i2c_adapter adap;
> + struct mutex i2c_lock;
> + struct virtqueue *vq;
> +};
> +
> +static void virtio_i2c_msg_done(struct virtqueue *vq)
> +{
> + struct virtio_i2c *vi = vq->vdev->priv;
> +
> + complete(&vi->completion);
> +}
> +
> +static int virtio_i2c_add_msg(struct virtqueue *vq,
> + struct virtio_i2c_msg *vmsg,
> + struct i2c_msg *msg)
> +{
> + struct scatterlist *sgs[3], hdr, bout, bin, status;
> + int outcnt = 0, incnt = 0;
> +
> + if (!msg->len)
> + return -EINVAL;
> +
> + vmsg->hdr.addr = msg->addr;
> + vmsg->hdr.flags = msg->flags;
> + vmsg->hdr.len = msg->len;


Missing endian conversion?


> +
> + vmsg->buf = kzalloc(vmsg->hdr.len, GFP_KERNEL);
> + if (!vmsg->buf)
> + return -ENOMEM;
> +
> + sg_init_one(&hdr, &vmsg->hdr, sizeof(struct virtio_i2c_hdr));
> + sgs[outcnt++] = &hdr;
> + if (vmsg->hdr.flags & I2C_M_RD) {
> + sg_init_one(&bin, vmsg->buf, msg->len);
> + sgs[outcnt + incnt++] = &bin;
> + } else {
> + memcpy(vmsg->buf, msg->buf, msg->len);
> + sg_init_one(&bout, vmsg->buf, msg->len);
> + sgs[outcnt++] = &bout;
> + }
> + sg_init_one(&status, &vmsg->status, sizeof(vmsg->status));
> + sgs[outcnt + incnt++] = &status;
> +
> + return virtqueue_add_sgs(vq, sgs, outcnt, incnt, vmsg, GFP_KERNEL);
> +}
> +
> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
> + struct virtio_i2c *vi = i2c_get_adapdata(adap);
> + struct virtio_i2c_msg *vmsg_o, *vmsg_i;
> + struct virtqueue *vq = vi->vq;
> + unsigned long time_left;
> + int len, i, ret = 0;
> +
> + vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
> + if (!vmsg_o)
> + return -ENOMEM;


It looks to me we can avoid the allocation by embedding virtio_i2c_msg
into struct virtio_i2c;


> +
> + mutex_lock(&vi->i2c_lock);
> + vmsg_o->buf = NULL;
> + for (i = 0; i < num; i++) {
> + ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
> + if (ret) {
> + dev_err(&adap->dev, "failed to add msg[%d] to virtqueue.\n", i);
> + goto err_unlock_free;
> + }
> +
> + virtqueue_kick(vq);
> +
> + time_left = wait_for_completion_timeout(&vi->completion, adap->timeout);
> + if (!time_left) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i, msgs[i].addr);
> + ret = i;
> + goto err_unlock_free;
> + }
> +
> + vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
> + if (vmsg_i) {
> + /* vmsg_i should point to the same address with vmsg_o */
> + if (vmsg_i != vmsg_o) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue error.\n",
> + i, vmsg_i->hdr.addr);
> + ret = i;
> + goto err_unlock_free;
> + }


Does this imply in order completion of i2c device?  (E.g what happens if
multiple virtio i2c requests are submitted)

Btw, this always use a single descriptor once a time which makes me
suspect if a virtqueue(virtio) is really needed. It looks to me we can
utilize the virtqueue by submit the request in a batch.

> + if (vmsg_i->status != VIRTIO_I2C_MSG_OK) {
> + dev_err(&adap->dev, "msg[%d]: addr=0x%x error=%d.\n",
> + i, vmsg_i->hdr.addr, vmsg_i->status);
> + ret = i;
> + goto err_unlock_free;
> + }
> + if ((vmsg_i->hdr.flags & I2C_M_RD) && vmsg_i->hdr.len)
> + memcpy(msgs[i].buf, vmsg_i->buf, vmsg_i->hdr.len);
> +
> + kfree(vmsg_i->buf);
> + vmsg_i->buf = NULL;
> + }
> + reinit_completion(&vi->completion);
> + }
> + if (i == num)
> + ret = num;
> +
> +err_unlock_free:
> + mutex_unlock(&vi->i2c_lock);
> + kfree(vmsg_o->buf);
> + kfree(vmsg_o);
> + return ret;
> +}
> +
> +static void virtio_i2c_del_vqs(struct virtio_device *vdev)
> +{
> + vdev->config->reset(vdev);


Why need reset here?

Thanks


> + vdev->config->del_vqs(vdev);
> +}
> +
> +static int virtio_i2c_setup_vqs(struct virtio_i2c *vi)
> +{
> + struct virtio_device *vdev = vi->vdev;
> +
> + vi->vq = virtio_find_single_vq(vdev, virtio_i2c_msg_done, "i2c-msg");


We've in the scope of ic2, so "msg" should be sufficient.


> + return PTR_ERR_OR_ZERO(vi->vq);
> +}
> +
> +static u32 virtio_i2c_func(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static struct i2c_algorithm virtio_algorithm = {
> + .master_xfer = virtio_i2c_xfer,
> + .functionality = virtio_i2c_func,
> +};
> +
> +static struct i2c_adapter virtio_adapter = {
> + .owner = THIS_MODULE,
> + .name = "Virtio I2C Adapter",
> + .class = I2C_CLASS_DEPRECATED,
> + .algo = &virtio_algorithm,
> +};
> +
> +static int virtio_i2c_probe(struct virtio_device *vdev)
> +{
> + struct device *pdev = vdev->dev.parent;
> + struct virtio_i2c *vi;
> + int ret;
> +
> + vi = devm_kzalloc(&vdev->dev, sizeof(*vi), GFP_KERNEL);
> + if (!vi)
> + return -ENOMEM;
> +
> + vdev->priv = vi;
> + vi->vdev = vdev;
> +
> + mutex_init(&vi->i2c_lock);
> + init_completion(&vi->completion);
> +
> + ret = virtio_i2c_setup_vqs(vi);
> + if (ret)
> + return ret;
> +
> + vi->adap = virtio_adapter;
> + i2c_set_adapdata(&vi->adap, vi);
> + vi->adap.dev.parent = &vdev->dev;
> + /* Setup ACPI node for slave devices which will be probed through ACPI */
> + ACPI_COMPANION_SET(&vi->adap.dev, ACPI_COMPANION(pdev));
> + vi->adap.timeout = HZ / 10;
> + ret = i2c_add_adapter(&vi->adap);
> + if (ret) {
> + dev_err(&vdev->dev, "failed to add virtio-i2c adapter.\n");
> + virtio_i2c_del_vqs(vdev);
> + }
> +
> + return ret;
> +}
> +
> +static void virtio_i2c_remove(struct virtio_device *vdev)
> +{
> + struct virtio_i2c *vi = vdev->priv;
> +
> + i2c_del_adapter(&vi->adap);
> + virtio_i2c_del_vqs(vdev);
> +}
> +
> +static struct virtio_device_id id_table[] = {
> + { VIRTIO_ID_I2C_ADPTER, VIRTIO_DEV_ANY_ID },
> + {}
> +};
> +MODULE_DEVICE_TABLE(virtio, id_table);
> +
> +static int __maybe_unused virtio_i2c_freeze(struct virtio_device *vdev)
> +{
> + virtio_i2c_del_vqs(vdev);
> + return 0;
> +}
> +
> +static int __maybe_unused virtio_i2c_restore(struct virtio_device *vdev)
> +{
> + return virtio_i2c_setup_vqs(vdev->priv);
> +}
> +
> +static struct virtio_driver virtio_i2c_driver = {
> + .id_table = id_table,
> + .probe = virtio_i2c_probe,
> + .remove = virtio_i2c_remove,
> + .driver = {
> + .name = "i2c_virtio",
> + },
> +#ifdef CONFIG_PM_SLEEP
> + .freeze = virtio_i2c_freeze,
> + .restore = virtio_i2c_restore,
> +#endif
> +};
> +module_virtio_driver(virtio_i2c_driver);
> +
> +MODULE_DESCRIPTION("Virtio i2c bus driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index b052355..398ef2d 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -48,5 +48,6 @@
> #define VIRTIO_ID_FS 26 /* virtio filesystem */
> #define VIRTIO_ID_PMEM 27 /* virtio pmem */
> #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
> +#define VIRTIO_ID_I2C_ADPTER 34 /* virtio i2c adpter */
>
> #endif /* _LINUX_VIRTIO_IDS_H */

2020-09-04 05:30:07

by Jie Deng

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 17:58, Michael S. Tsirkin wrote:
> On Thu, Sep 03, 2020 at 01:34:45PM +0800, Jie Deng wrote:
>> Add an I2C bus driver for virtio para-virtualization.
>>
>> The controller can be emulated by the backend driver in
>> any device model software by following the virtio protocol.
>>
>> This driver communicates with the backend driver through a
>> virtio I2C message structure which includes following parts:
>>
>> - Header: i2c_msg addr, flags, len.
>> - Data buffer: the pointer to the i2c msg data.
>> - Status: the processing result from the backend.
>>
>> People may implement different backend drivers to emulate
>> different controllers according to their needs. A backend
>> example can be found in the device model of the open source
>> project ACRN. For more information, please refer to
>> https://projectacrn.org.
>>
>> The virtio device ID 34 is used for this I2C adpter since IDs
>> before 34 have been reserved by other virtio devices.
> Please reserve the ID with the virtio tc so no one conflicts.
>
Sure. I will send a patch to request the ID.


>
> +
> +/**
> + * struct virtio_i2c_hdr - the virtio I2C message header structure
> + * @addr: i2c_msg addr, the slave address
> + * @flags: i2c_msg flags
> + * @len: i2c_msg len
> + */
> +struct virtio_i2c_hdr {
> + __virtio16 addr;
> + __virtio16 flags;
> + __virtio16 len;
> +} __packed;
> virtio16 is for legacy devices, modern ones should be __le.
> and we don't really need to pack it I think.

Right. I will fix these. Thanks.




2020-09-04 06:10:07

by Jie Deng

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 18:20, Andy Shevchenko wrote:
> On Thu, Sep 03, 2020 at 01:34:45PM +0800, Jie Deng wrote:
>> Add an I2C bus driver for virtio para-virtualization.
>>
>> The controller can be emulated by the backend driver in
>> any device model software by following the virtio protocol.
>>
>> This driver communicates with the backend driver through a
>> virtio I2C message structure which includes following parts:
>>
>> - Header: i2c_msg addr, flags, len.
>> - Data buffer: the pointer to the i2c msg data.
>> - Status: the processing result from the backend.
>>
>> People may implement different backend drivers to emulate
>> different controllers according to their needs. A backend
>> example can be found in the device model of the open source
>> project ACRN. For more information, please refer to
>> https://projectacrn.org.
>>
>> The virtio device ID 34 is used for this I2C adpter since IDs
>> before 34 have been reserved by other virtio devices.
> Seems it's slightly different version to what I have reviewed internally.
> My comments below. (I admit that some of them maybe new)
>
> ...

Yeah... Some new devices have been added into the virtio spec during
these days.


>> +/**
>> + * struct virtio_i2c_hdr - the virtio I2C message header structure
>> + * @addr: i2c_msg addr, the slave address
>> + * @flags: i2c_msg flags
>> + * @len: i2c_msg len
>> + */
>> +struct virtio_i2c_hdr {
>> + __virtio16 addr;
>> + __virtio16 flags;
>> + __virtio16 len;
>> +} __packed;
> As Misha noticed and somewhere I saw 0-day reports these should be carefully
> taken care of.
>
> ...

Sure. Will fix these.


>> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
>> +{
>> + struct virtio_i2c *vi = i2c_get_adapdata(adap);
>> + struct virtio_i2c_msg *vmsg_o, *vmsg_i;
>> + struct virtqueue *vq = vi->vq;
>> + unsigned long time_left;
>> + int len, i, ret = 0;
>> +
>> + vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
>> + if (!vmsg_o)
>> + return -ENOMEM;
>> +
>> + mutex_lock(&vi->i2c_lock);
>> + vmsg_o->buf = NULL;
>> + for (i = 0; i < num; i++) {
>> + ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
>> + if (ret) {
>> + dev_err(&adap->dev, "failed to add msg[%d] to virtqueue.\n", i);
>> + goto err_unlock_free;
> break;

OK.


>> + }
>> +
>> + virtqueue_kick(vq);
>> +
>> + time_left = wait_for_completion_timeout(&vi->completion, adap->timeout);
>> + if (!time_left) {
>> + dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i, msgs[i].addr);
>> + ret = i;
>> + goto err_unlock_free;
> break;
>
> And so on.
OK.
>> + }
>> +
>> + vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
>> + if (vmsg_i) {
>> + /* vmsg_i should point to the same address with vmsg_o */
>> + if (vmsg_i != vmsg_o) {
>> + dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue error.\n",
>> + i, vmsg_i->hdr.addr);
>> + ret = i;
>> + goto err_unlock_free;
>> + }
>> + if (vmsg_i->status != VIRTIO_I2C_MSG_OK) {
>> + dev_err(&adap->dev, "msg[%d]: addr=0x%x error=%d.\n",
>> + i, vmsg_i->hdr.addr, vmsg_i->status);
>> + ret = i;
>> + goto err_unlock_free;
>> + }
>> + if ((vmsg_i->hdr.flags & I2C_M_RD) && vmsg_i->hdr.len)
>> + memcpy(msgs[i].buf, vmsg_i->buf, vmsg_i->hdr.len);
>> +
>> + kfree(vmsg_i->buf);
>> + vmsg_i->buf = NULL;
>> + }
>> + reinit_completion(&vi->completion);
>> + }
>> + if (i == num)
>> + ret = num;
> And this conditional seems a dup of the for-loop successfully iterating over
> entire queue.
You are right.
We may save several lines of code by using "Return (ret<0) ? ret : i" at
the end.


>> +err_unlock_free:
> Redundant.
OK.
>> + mutex_unlock(&vi->i2c_lock);
>> + kfree(vmsg_o->buf);
>> + kfree(vmsg_o);
>> + return ret;
>> +}
> ...
>
>> + vi->adap.timeout = HZ / 10;
> + Blank line.
OK.
>> + ret = i2c_add_adapter(&vi->adap);
>> + if (ret) {
>> + dev_err(&vdev->dev, "failed to add virtio-i2c adapter.\n");
>> + virtio_i2c_del_vqs(vdev);
> Usually we do clean up followed by message.
I will change the order. Thank you.
>> + }
>> +
>> + return ret;
>

2020-09-04 13:29:17

by Jie Deng

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/4 12:06, Jason Wang wrote:
>
>> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> index 293e7a0..70c8e30 100644
>> --- a/drivers/i2c/busses/Kconfig
>> +++ b/drivers/i2c/busses/Kconfig
>> @@ -21,6 +21,17 @@ config I2C_ALI1535
>>         This driver can also be built as a module.  If so, the module
>>         will be called i2c-ali1535.
>>   +config I2C_VIRTIO
>> +    tristate "Virtio I2C Adapter"
>> +    depends on VIRTIO
>
>
> I guess it should depend on some I2C module here.
>
The dependency of I2C is included in the Kconfig in its parent directory.
So there is nothing special to add here.


>
>>
>> +struct virtio_i2c_msg {
>> +    struct virtio_i2c_hdr hdr;
>> +    char *buf;
>> +    u8 status;
>
>
> Any reason for separating status out of virtio_i2c_hdr?
>
The status is not from i2c_msg. So I put it out of virtio_i2c_hdr.

>
>> +};
>> +
>> +/**
>> + * struct virtio_i2c - virtio I2C data
>> + * @vdev: virtio device for this controller
>> + * @completion: completion of virtio I2C message
>> + * @adap: I2C adapter for this controller
>> + * @i2c_lock: lock for virtqueue processing
>> + * @vq: the virtio virtqueue for communication
>> + */
>> +struct virtio_i2c {
>> +    struct virtio_device *vdev;
>> +    struct completion completion;
>> +    struct i2c_adapter adap;
>> +    struct mutex i2c_lock;
>> +    struct virtqueue *vq;
>> +};
>> +
>> +static void virtio_i2c_msg_done(struct virtqueue *vq)
>> +{
>> +    struct virtio_i2c *vi = vq->vdev->priv;
>> +
>> +    complete(&vi->completion);
>> +}
>> +
>> +static int virtio_i2c_add_msg(struct virtqueue *vq,
>> +                  struct virtio_i2c_msg *vmsg,
>> +                  struct i2c_msg *msg)
>> +{
>> +    struct scatterlist *sgs[3], hdr, bout, bin, status;
>> +    int outcnt = 0, incnt = 0;
>> +
>> +    if (!msg->len)
>> +        return -EINVAL;
>> +
>> +    vmsg->hdr.addr = msg->addr;
>> +    vmsg->hdr.flags = msg->flags;
>> +    vmsg->hdr.len = msg->len;
>
>
> Missing endian conversion?
>
You are right. Need conversion here.
>
>> +
>> +    vmsg->buf = kzalloc(vmsg->hdr.len, GFP_KERNEL);
>> +    if (!vmsg->buf)
>> +        return -ENOMEM;
>> +
>> +    sg_init_one(&hdr, &vmsg->hdr, sizeof(struct virtio_i2c_hdr));
>> +    sgs[outcnt++] = &hdr;
>> +    if (vmsg->hdr.flags & I2C_M_RD) {
>> +        sg_init_one(&bin, vmsg->buf, msg->len);
>> +        sgs[outcnt + incnt++] = &bin;
>> +    } else {
>> +        memcpy(vmsg->buf, msg->buf, msg->len);
>> +        sg_init_one(&bout, vmsg->buf, msg->len);
>> +        sgs[outcnt++] = &bout;
>> +    }
>> +    sg_init_one(&status, &vmsg->status, sizeof(vmsg->status));
>> +    sgs[outcnt + incnt++] = &status;
>> +
>> +    return virtqueue_add_sgs(vq, sgs, outcnt, incnt, vmsg, GFP_KERNEL);
>> +}
>> +
>> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg
>> *msgs, int num)
>> +{
>> +    struct virtio_i2c *vi = i2c_get_adapdata(adap);
>> +    struct virtio_i2c_msg *vmsg_o, *vmsg_i;
>> +    struct virtqueue *vq = vi->vq;
>> +    unsigned long time_left;
>> +    int len, i, ret = 0;
>> +
>> +    vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
>> +    if (!vmsg_o)
>> +        return -ENOMEM;
>
>
> It looks to me we can avoid the allocation by embedding virtio_i2c_msg
> into struct virtio_i2c;
>
Yeah... That's better. Thanks.


>
>> +
>> +    mutex_lock(&vi->i2c_lock);
>> +    vmsg_o->buf = NULL;
>> +    for (i = 0; i < num; i++) {
>> +        ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
>> +        if (ret) {
>> +            dev_err(&adap->dev, "failed to add msg[%d] to
>> virtqueue.\n", i);
>> +            goto err_unlock_free;
>> +        }
>> +
>> +        virtqueue_kick(vq);
>> +
>> +        time_left = wait_for_completion_timeout(&vi->completion,
>> adap->timeout);
>> +        if (!time_left) {
>> +            dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i,
>> msgs[i].addr);
>> +            ret = i;
>> +            goto err_unlock_free;
>> +        }
>> +
>> +        vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
>> +        if (vmsg_i) {
>> +            /* vmsg_i should point to the same address with vmsg_o */
>> +            if (vmsg_i != vmsg_o) {
>> +                dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue
>> error.\n",
>> +                    i, vmsg_i->hdr.addr);
>> +                ret = i;
>> +                goto err_unlock_free;
>> +            }
>
>
> Does this imply in order completion of i2c device?  (E.g what happens
> if multiple virtio i2c requests are submitted)
>
> Btw, this always use a single descriptor once a time which makes me
> suspect if a virtqueue(virtio) is really needed. It looks to me we can
> utilize the virtqueue by submit the request in a batch.
>
I'm afraid not all physical devices support batch. I'd like to keep the
current design and consider
your suggestion as a possible optimization in the future.

Thanks.


>>
>> +}
>> +
>> +static void virtio_i2c_del_vqs(struct virtio_device *vdev)
>> +{
>> +    vdev->config->reset(vdev);
>
>
> Why need reset here?
>
> Thanks
>
I'm following what other virtio drivers do.
They reset the devices before they clean up the queues.


>
>> +    vdev->config->del_vqs(vdev);
>> +}
>> +
>> +static int virtio_i2c_setup_vqs(struct virtio_i2c *vi)
>> +{
>> +    struct virtio_device *vdev = vi->vdev;
>> +
>> +    vi->vq = virtio_find_single_vq(vdev, virtio_i2c_msg_done,
>> "i2c-msg");
>
>
> We've in the scope of ic2, so "msg" should be sufficient.
>
>
OK. Will change this name. Thanks.


>> +    return PTR_ERR_OR_ZERO(vi->vq);

2020-09-07 05:47:41

by Jason Wang

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/4 下午9:21, Jie Deng wrote:
>
> On 2020/9/4 12:06, Jason Wang wrote:
>>
>>> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>>> index 293e7a0..70c8e30 100644
>>> --- a/drivers/i2c/busses/Kconfig
>>> +++ b/drivers/i2c/busses/Kconfig
>>> @@ -21,6 +21,17 @@ config I2C_ALI1535
>>>         This driver can also be built as a module.  If so, the module
>>>         will be called i2c-ali1535.
>>>   +config I2C_VIRTIO
>>> +    tristate "Virtio I2C Adapter"
>>> +    depends on VIRTIO
>>
>>
>> I guess it should depend on some I2C module here.
>>
> The dependency of I2C is included in the Kconfig in its parent directory.
> So there is nothing special to add here.


Ok.


>
>
>>
>>>
>>> +struct virtio_i2c_msg {
>>> +    struct virtio_i2c_hdr hdr;
>>> +    char *buf;
>>> +    u8 status;
>>
>>
>> Any reason for separating status out of virtio_i2c_hdr?
>>
> The status is not from i2c_msg.


You meant ic2_hdr? You embed status in virtio_i2c_msg anyway.


> So I put it out of virtio_i2c_hdr.


Something like status or response is pretty common in virtio request
(e.g net or scsi), if no special reason, it's better to keep it in the hdr.


>
>>
>>> +};
>>> +
>>> +/**
>>> + * struct virtio_i2c - virtio I2C data
>>> + * @vdev: virtio device for this controller
>>> + * @completion: completion of virtio I2C message
>>> + * @adap: I2C adapter for this controller
>>> + * @i2c_lock: lock for virtqueue processing
>>> + * @vq: the virtio virtqueue for communication
>>> + */
>>> +struct virtio_i2c {
>>> +    struct virtio_device *vdev;
>>> +    struct completion completion;
>>> +    struct i2c_adapter adap;
>>> +    struct mutex i2c_lock;
>>> +    struct virtqueue *vq;
>>> +};
>>> +
>>> +static void virtio_i2c_msg_done(struct virtqueue *vq)
>>> +{
>>> +    struct virtio_i2c *vi = vq->vdev->priv;
>>> +
>>> +    complete(&vi->completion);
>>> +}
>>> +
>>> +static int virtio_i2c_add_msg(struct virtqueue *vq,
>>> +                  struct virtio_i2c_msg *vmsg,
>>> +                  struct i2c_msg *msg)
>>> +{
>>> +    struct scatterlist *sgs[3], hdr, bout, bin, status;
>>> +    int outcnt = 0, incnt = 0;
>>> +
>>> +    if (!msg->len)
>>> +        return -EINVAL;
>>> +
>>> +    vmsg->hdr.addr = msg->addr;
>>> +    vmsg->hdr.flags = msg->flags;
>>> +    vmsg->hdr.len = msg->len;
>>
>>
>> Missing endian conversion?
>>
> You are right. Need conversion here.
>>
>>> +
>>> +    vmsg->buf = kzalloc(vmsg->hdr.len, GFP_KERNEL);
>>> +    if (!vmsg->buf)
>>> +        return -ENOMEM;
>>> +
>>> +    sg_init_one(&hdr, &vmsg->hdr, sizeof(struct virtio_i2c_hdr));
>>> +    sgs[outcnt++] = &hdr;
>>> +    if (vmsg->hdr.flags & I2C_M_RD) {
>>> +        sg_init_one(&bin, vmsg->buf, msg->len);
>>> +        sgs[outcnt + incnt++] = &bin;
>>> +    } else {
>>> +        memcpy(vmsg->buf, msg->buf, msg->len);
>>> +        sg_init_one(&bout, vmsg->buf, msg->len);
>>> +        sgs[outcnt++] = &bout;
>>> +    }
>>> +    sg_init_one(&status, &vmsg->status, sizeof(vmsg->status));
>>> +    sgs[outcnt + incnt++] = &status;
>>> +
>>> +    return virtqueue_add_sgs(vq, sgs, outcnt, incnt, vmsg,
>>> GFP_KERNEL);
>>> +}
>>> +
>>> +static int virtio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg
>>> *msgs, int num)
>>> +{
>>> +    struct virtio_i2c *vi = i2c_get_adapdata(adap);
>>> +    struct virtio_i2c_msg *vmsg_o, *vmsg_i;
>>> +    struct virtqueue *vq = vi->vq;
>>> +    unsigned long time_left;
>>> +    int len, i, ret = 0;
>>> +
>>> +    vmsg_o = kzalloc(sizeof(*vmsg_o), GFP_KERNEL);
>>> +    if (!vmsg_o)
>>> +        return -ENOMEM;
>>
>>
>> It looks to me we can avoid the allocation by embedding
>> virtio_i2c_msg into struct virtio_i2c;
>>
> Yeah... That's better. Thanks.
>
>
>>
>>> +
>>> +    mutex_lock(&vi->i2c_lock);
>>> +    vmsg_o->buf = NULL;
>>> +    for (i = 0; i < num; i++) {
>>> +        ret = virtio_i2c_add_msg(vq, vmsg_o, &msgs[i]);
>>> +        if (ret) {
>>> +            dev_err(&adap->dev, "failed to add msg[%d] to
>>> virtqueue.\n", i);
>>> +            goto err_unlock_free;
>>> +        }
>>> +
>>> +        virtqueue_kick(vq);
>>> +
>>> +        time_left = wait_for_completion_timeout(&vi->completion,
>>> adap->timeout);
>>> +        if (!time_left) {
>>> +            dev_err(&adap->dev, "msg[%d]: addr=0x%x timeout.\n", i,
>>> msgs[i].addr);
>>> +            ret = i;
>>> +            goto err_unlock_free;
>>> +        }
>>> +
>>> +        vmsg_i = (struct virtio_i2c_msg *)virtqueue_get_buf(vq, &len);
>>> +        if (vmsg_i) {
>>> +            /* vmsg_i should point to the same address with vmsg_o */
>>> +            if (vmsg_i != vmsg_o) {
>>> +                dev_err(&adap->dev, "msg[%d]: addr=0x%x virtqueue
>>> error.\n",
>>> +                    i, vmsg_i->hdr.addr);
>>> +                ret = i;
>>> +                goto err_unlock_free;
>>> +            }
>>
>>
>> Does this imply in order completion of i2c device?  (E.g what happens
>> if multiple virtio i2c requests are submitted)
>>
>> Btw, this always use a single descriptor once a time which makes me
>> suspect if a virtqueue(virtio) is really needed. It looks to me we
>> can utilize the virtqueue by submit the request in a batch.
>>
> I'm afraid not all physical devices support batch.


Yes but I think I meant for the virtio device not the physical one. It's
impossible to forbid batching if you have a queue anyway ...


> I'd like to keep the current design and consider
> your suggestion as a possible optimization in the future.
>
> Thanks.
>
>
>>>
>>> +}
>>> +
>>> +static void virtio_i2c_del_vqs(struct virtio_device *vdev)
>>> +{
>>> +    vdev->config->reset(vdev);
>>
>>
>> Why need reset here?
>>
>> Thanks
>>
> I'm following what other virtio drivers do.
> They reset the devices before they clean up the queues.


You're ring.

Thanks


>
>
>>
>>> +    vdev->config->del_vqs(vdev);
>>> +}
>>> +
>>> +static int virtio_i2c_setup_vqs(struct virtio_i2c *vi)
>>> +{
>>> +    struct virtio_device *vdev = vi->vdev;
>>> +
>>> +    vi->vq = virtio_find_single_vq(vdev, virtio_i2c_msg_done,
>>> "i2c-msg");
>>
>>
>> We've in the scope of ic2, so "msg" should be sufficient.
>>
>>
> OK. Will change this name. Thanks.
>
>
>>> +    return PTR_ERR_OR_ZERO(vi->vq);
>

2020-09-09 08:58:09

by Jason Wang

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/8 上午9:40, Jie Deng wrote:
>
>
> On 2020/9/7 13:40, Jason Wang wrote:
>>
>>>
>>>
>>>>
>>>>>
>>>>> +struct virtio_i2c_msg {
>>>>> +    struct virtio_i2c_hdr hdr;
>>>>> +    char *buf;
>>>>> +    u8 status;
>>>>
>>>>
>>>> Any reason for separating status out of virtio_i2c_hdr?
>>>>
>>> The status is not from i2c_msg.
>>
>>
>> You meant ic2_hdr? You embed status in virtio_i2c_msg anyway.
>>
>>
> The "i2c_msg" structure defined in i2c.h.
>
>>> So I put it out of virtio_i2c_hdr.
>>
>>
>> Something like status or response is pretty common in virtio request
>> (e.g net or scsi), if no special reason, it's better to keep it in
>> the hdr.
>>
> Mainly based on IN or OUT.
>
> The addr, flags and len are from "i2c_msg". They are put in one
> structure as an OUT**scatterlist.
> The buf can be an OUT**or an IN scatterlist depending on write or read.
> The status is a result from the backend  which is defined as an IN
> scatterlis.


Ok. I get this.

Thanks


2020-09-09 09:03:27

by Jason Wang

[permalink] [raw]
Subject: Re: [PATCH] i2c: virtio: add a virtio i2c frontend driver


On 2020/9/3 下午1:34, Jie Deng wrote:
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -6,6 +6,9 @@
> # ACPI drivers
> obj-$(CONFIG_I2C_SCMI) += i2c-scmi.o
>
> +# VIRTIO I2C host controller driver
> +obj-$(CONFIG_I2C_VIRTIO) += i2c-virtio.o
> +
> # PC SMBus host controller drivers
> obj-$(CONFIG_I2C_ALI1535) += i2c-ali1535.o
> obj-$(CONFIG_I2C_ALI1563) += i2c-ali1563.o
> diff --git a/drivers/i2c/busses/i2c-virtio.c b/drivers/i2c/busses/i2c-virtio.c
> new file mode 100644
> index 0000000..47f9fd1
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-virtio.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Virtio I2C Bus Driver
> + *
> + * Copyright (c) 2020 Intel Corporation. All rights reserved.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/io.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/wait.h>
> +
> +#include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
> +#include <linux/virtio_config.h>
> +
> +#define VIRTIO_I2C_MSG_OK 0
> +#define VIRTIO_I2C_MSG_ERR 1
> +
> +/**
> + * struct virtio_i2c_hdr - the virtio I2C message header structure
> + * @addr: i2c_msg addr, the slave address
> + * @flags: i2c_msg flags
> + * @len: i2c_msg len
> + */
> +struct virtio_i2c_hdr {
> + __virtio16 addr;
> + __virtio16 flags;
> + __virtio16 len;
> +} __packed;


Btw, this part should belong to uAPI, and you need to define the status
in uAPI.

Thanks