diff options
Diffstat (limited to 'drivers/i2c/busses/xen-i2cback.c')
-rw-r--r-- | drivers/i2c/busses/xen-i2cback.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/drivers/i2c/busses/xen-i2cback.c b/drivers/i2c/busses/xen-i2cback.c new file mode 100644 index 000000000000..b9036b864451 --- /dev/null +++ b/drivers/i2c/busses/xen-i2cback.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018-2019 NXP + * + * Peng Fan <peng.fan@nxp.com> + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/slab.h> + +#include <xen/xen.h> +#include <xen/events.h> +#include <xen/xenbus.h> +#include <xen/grant_table.h> +#include <xen/page.h> + +#include <xen/interface/grant_table.h> +#include <xen/interface/io/i2cif.h> + +struct i2cback_info { + domid_t domid; + u32 irq; + u64 handle; + struct xenbus_device *i2cdev; + spinlock_t i2c_ring_lock; + struct i2cif_back_ring i2c_ring; + int is_connected; + int ring_error; + struct i2c_adapter *adapter; + u32 num_slaves; + u32 *allowed_slaves; +}; + +static bool i2cback_access_allowed(struct i2cback_info *info, + struct i2cif_request *req) +{ + int i; + + if (req->is_smbus) {/*check for smbus access permission*/ + for (i = 0; i < info->num_slaves; i++) + if (req->addr == info->allowed_slaves[i]) + return true; + + return false; + } + + /*check for master_xfer access permission*/ + if (req->num_msg == I2CIF_MAX_MSG) { + if (req->msg[0].addr != req->msg[1].addr) + return false; + } + + for (i = 0; i < info->num_slaves; i++) { + if (req->msg[0].addr == info->allowed_slaves[i]) + return true; + } + + return false; +} + +static bool i2cback_handle_int(struct i2cback_info *info) +{ + struct i2cif_back_ring *i2c_ring = &info->i2c_ring; + struct i2cif_request req; + struct i2cif_response *res; + RING_IDX rc, rp; + int more_to_do, notify, num_msg = 0, ret; + struct i2c_msg msg[I2CIF_MAX_MSG]; + union i2c_smbus_data smbus_data; + char tmp_buf[I2CIF_BUF_LEN]; + unsigned long flags; + bool allow_access; + int i; + + rc = i2c_ring->req_cons; + rp = i2c_ring->sring->req_prod; + rmb(); /* req_cons is written by frontend. */ + + if (RING_REQUEST_PROD_OVERFLOW(i2c_ring, rp)) { + rc = i2c_ring->rsp_prod_pvt; + dev_err(&info->i2cdev->dev, "ring overflow\n"); + info->ring_error = 1; + return 0; + } + + while (rc != rp) { + if (RING_REQUEST_CONS_OVERFLOW(i2c_ring, rc)) { + dev_err(&info->i2cdev->dev, "%s overflow\n", __func__); + break; + } + + req = *RING_GET_REQUEST(i2c_ring, rc); + allow_access = i2cback_access_allowed(info, &req); + if (allow_access && !req.is_smbus) { + /* Write/Read sequence */ + num_msg = req.num_msg; + if (num_msg > I2CIF_MAX_MSG) + num_msg = I2CIF_MAX_MSG; + + for (i = 0; i < num_msg; i++) { + msg[i].addr = req.msg[i].addr; + msg[i].len = req.msg[i].len; + msg[i].flags = 0; + if (req.msg[i].flags & I2CIF_M_RD) + msg[i].flags |= I2C_M_RD; + if (req.msg[i].flags & I2CIF_M_TEN) + msg[i].flags |= I2C_M_TEN; + if (req.msg[i].flags & I2CIF_M_RECV_LEN) + msg[i].flags |= I2C_M_RECV_LEN; + if (req.msg[i].flags & I2CIF_M_NO_RD_ACK) + msg[i].flags |= I2C_M_NO_RD_ACK; + if (req.msg[i].flags & I2CIF_M_IGNORE_NAK) + msg[i].flags |= I2C_M_IGNORE_NAK; + if (req.msg[i].flags & I2CIF_M_REV_DIR_ADDR) + msg[i].flags |= I2C_M_REV_DIR_ADDR; + if (req.msg[i].flags & I2CIF_M_NOSTART) + msg[i].flags |= I2C_M_NOSTART; + if (req.msg[i].flags & I2CIF_M_STOP) + msg[i].flags |= I2C_M_STOP; + } + + if ((num_msg == 2) && + (!(msg[0].flags & I2C_M_RD)) && + (msg[1].flags & I2C_M_RD)) { + + /* overwrite the remote buf with local buf */ + msg[0].buf = tmp_buf; + msg[1].buf = tmp_buf; + + /* msg[0] write buf */ + memcpy(tmp_buf, req.write_buf, I2CIF_BUF_LEN); + ret = i2c_transfer(info->adapter, msg, + num_msg); + } else if (num_msg == 1) { + msg[0].buf = tmp_buf; + if (!(msg[0].flags & I2C_M_RD)) + memcpy(tmp_buf, req.write_buf, + I2CIF_BUF_LEN); + ret = i2c_transfer(info->adapter, msg, + req.num_msg); + } else { + dev_dbg(&info->i2cdev->dev, "too many msgs\n"); + + ret = -EIO; + } + } else if (allow_access && req.is_smbus) { + memcpy(&smbus_data, &req.write_buf, sizeof(smbus_data)); + + ret = i2c_smbus_xfer(info->adapter, + req.addr, + req.flags, + req.read_write, + req.command, + req.protocol, + &smbus_data); + } + + spin_lock_irqsave(&info->i2c_ring_lock, flags); + res = RING_GET_RESPONSE(&info->i2c_ring, + info->i2c_ring.rsp_prod_pvt); + + if (allow_access && !req.is_smbus) { + res->result = ret; + + if ((req.num_msg == 2) && + (!(msg[0].flags & I2C_M_RD)) && + (msg[1].flags & I2C_M_RD) && (ret >= 0)) { + memcpy(res->read_buf, tmp_buf, I2CIF_BUF_LEN); + } else if (req.num_msg == 1) { + if ((msg[0].flags & I2C_M_RD) && (ret >= 0)) + memcpy(res->read_buf, tmp_buf, + I2CIF_BUF_LEN); + } + } else if (allow_access && req.is_smbus) { + if (req.read_write == I2C_SMBUS_READ) + memcpy(&res->read_buf, &smbus_data, sizeof(smbus_data)); + res->result = ret; + } else + res->result = -EPERM; + + info->i2c_ring.rsp_prod_pvt++; + + barrier(); + RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&info->i2c_ring, + notify); + spin_unlock_irqrestore(&info->i2c_ring_lock, flags); + + if (notify) + notify_remote_via_irq(info->irq); + + i2c_ring->req_cons = ++rc; + + cond_resched(); + } + + RING_FINAL_CHECK_FOR_REQUESTS(i2c_ring, more_to_do); + + return !!more_to_do; +} + +static irqreturn_t i2cback_be_int(int irq, void *dev_id) +{ + struct i2cback_info *info = dev_id; + + if (info->ring_error) + return IRQ_HANDLED; + + while (i2cback_handle_int(info)) + cond_resched(); + + return IRQ_HANDLED; +} + +static int i2cback_map(struct i2cback_info *info, grant_ref_t *i2c_ring_ref, + evtchn_port_t evtchn) +{ + int err; + void *addr; + struct i2cif_sring *i2c_sring; + + if (info->irq) + return 0; + + err = xenbus_map_ring_valloc(info->i2cdev, i2c_ring_ref, 1, &addr); + if (err) + return err; + + i2c_sring = addr; + + BACK_RING_INIT(&info->i2c_ring, i2c_sring, PAGE_SIZE); + + err = bind_interdomain_evtchn_to_irq(info->domid, evtchn); + if (err < 0) + goto fail_evtchn; + info->irq = err; + + err = request_threaded_irq(info->irq, NULL, i2cback_be_int, + IRQF_ONESHOT, "xen-i2cback", info); + if (err) { + dev_err(&info->i2cdev->dev, "bind evtchn to irq failure!\n"); + goto free_irq; + } + + return 0; +free_irq: + unbind_from_irqhandler(info->irq, info); + info->irq = 0; + info->i2c_ring.sring = NULL; +fail_evtchn: + xenbus_unmap_ring_vfree(info->i2cdev, i2c_sring); + return err; +} + +static int i2cback_connect_rings(struct i2cback_info *info) +{ + struct xenbus_device *dev = info->i2cdev; + unsigned int i2c_ring_ref, evtchn; + int i, err; + char *buf; + u32 adapter_id; + + err = xenbus_scanf(XBT_NIL, dev->nodename, + "adapter", "%u", &adapter_id); + if (err != 1) { + xenbus_dev_fatal(dev, err, "%s reading adapter", dev->nodename); + return err; + } + + info->adapter = i2c_get_adapter(adapter_id); + if (!info->adapter) + return -ENODEV; + + err = xenbus_scanf(XBT_NIL, dev->nodename, + "num-slaves", "%u", &info->num_slaves); + if (err != 1) { + xenbus_dev_fatal(dev, err, "%s reading num-slaves", + dev->nodename); + return err; + } + + info->allowed_slaves = devm_kmalloc(&dev->dev, + info->num_slaves * sizeof(u32), + GFP_KERNEL); + if (!info->allowed_slaves) + return -ENOMEM; + + /* 128 bytes is enough */ + buf = kmalloc(128, GFP_KERNEL); + + for (i = 0; i < info->num_slaves; i++) { + snprintf(buf, 128, "%s/%d", dev->nodename, i); + err = xenbus_scanf(XBT_NIL, buf, "addr", "%x", + &info->allowed_slaves[i]); + if (err != 1) { + kfree(buf); + return err; + } + } + + kfree(buf); + + err = xenbus_gather(XBT_NIL, dev->otherend, + "ring-ref", "%u", &i2c_ring_ref, + "event-channel", "%u", &evtchn, NULL); + if (err) { + xenbus_dev_fatal(dev, err, + "reading %s/ring-ref and event-channel", + dev->otherend); + return err; + } + + dev_info(&info->i2cdev->dev, + "xen-pvi2c: ring-ref %u, event-channel %u\n", + i2c_ring_ref, evtchn); + + err = i2cback_map(info, &i2c_ring_ref, evtchn); + if (err) + xenbus_dev_fatal(dev, err, "mapping ring-ref %u evtchn %u", + i2c_ring_ref, evtchn); + + return err; +} + +static void i2cback_disconnect(struct i2cback_info *info) +{ + if (info->irq) { + unbind_from_irqhandler(info->irq, info); + info->irq = 0; + } + + if (info->i2c_ring.sring) { + xenbus_unmap_ring_vfree(info->i2cdev, info->i2c_ring.sring); + info->i2c_ring.sring = NULL; + } +} + +static void i2cback_frontend_changed(struct xenbus_device *dev, + enum xenbus_state frontend_state) +{ + struct i2cback_info *info = dev_get_drvdata(&dev->dev); + int ret; + + switch (frontend_state) { + case XenbusStateInitialised: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + break; + + case XenbusStateInitialising: + if (dev->state == XenbusStateClosed) { + dev_info(&dev->dev, + "xen-pvi2c: %s: prepare for reconnect\n", + dev->nodename); + xenbus_switch_state(dev, XenbusStateInitWait); + } + break; + case XenbusStateConnected: + if (dev->state == XenbusStateConnected) + break; + + xenbus_switch_state(dev, XenbusStateConnected); + + ret = i2cback_connect_rings(info); + if (ret) + xenbus_dev_fatal(dev, ret, "connect ring fail"); + break; + case XenbusStateClosing: + i2cback_disconnect(info); + xenbus_switch_state(dev, XenbusStateClosing); + break; + case XenbusStateClosed: + xenbus_switch_state(dev, XenbusStateClosed); + if (xenbus_dev_is_online(dev)) + break; + device_unregister(&dev->dev); + break; + case XenbusStateUnknown: + device_unregister(&dev->dev); + break; + + default: + xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", + frontend_state); + break; + } +} + +static struct i2cback_info *i2cback_alloc(domid_t domid, u64 handle) +{ + struct i2cback_info *info; + + info = kzalloc(sizeof(struct i2cback_info), GFP_KERNEL); + if (!info) + return NULL; + + info->domid = domid; + info->handle = handle; + spin_lock_init(&info->i2c_ring_lock); + info->ring_error = 0; + + return info; +} + +static int i2cback_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + struct i2cback_info *info; + unsigned long handle; + int err; + + if (kstrtoul(strrchr(dev->otherend, '/') + 1, 0, &handle)) + return -EINVAL; + + info = i2cback_alloc(dev->otherend_id, handle); + if (!info) { + xenbus_dev_fatal(dev, -ENOMEM, "Allocating backend interface"); + return -ENOMEM; + } + + info->i2cdev = dev; + dev_set_drvdata(&dev->dev, info); + + err = xenbus_switch_state(dev, XenbusStateInitWait); + if (err) + return err; + + return 0; +} + +static int i2cback_remove(struct xenbus_device *dev) +{ + struct i2cback_info *info = dev_get_drvdata(&dev->dev); + + if (!info) + return 0; + + i2cback_disconnect(info); + + kfree(info); + dev_set_drvdata(&dev->dev, NULL); + + dev_info(&dev->dev, "%s\n", __func__); + + return 0; +} + +static const struct xenbus_device_id i2cback_ids[] = { + { "vi2c" }, + { "" }, +}; + +static struct xenbus_driver i2cback_driver = { + .ids = i2cback_ids, + .probe = i2cback_probe, + .otherend_changed = i2cback_frontend_changed, + .remove = i2cback_remove, +}; + +static int __init i2cback_init(void) +{ + int err; + + if (!xen_domain()) + return -ENODEV; + + err = xenbus_register_backend(&i2cback_driver); + if (err) + return err; + + return 0; +} +module_init(i2cback_init); + +static void __exit i2cback_exit(void) +{ + xenbus_unregister_driver(&i2cback_driver); +} +module_exit(i2cback_exit); + +MODULE_ALIAS("xen-i2cback:vi2c"); +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_DESCRIPTION("Xen I2C backend driver (i2cback)"); +MODULE_LICENSE("GPL"); |