/* * Copyright 2018 NXP * * Peng Fan * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include 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; /* fall through if not online */ 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 "); MODULE_DESCRIPTION("Xen I2C backend driver (i2cback)"); MODULE_LICENSE("GPL");