mei: bus: move driver api functions at the start of the file
authorTomas Winkler <tomas.winkler@intel.com>
Thu, 23 Jul 2015 12:08:35 +0000 (15:08 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 4 Aug 2015 00:20:26 +0000 (17:20 -0700)
To make the file more organize move mei client driver api
to the start of the file and add Kdoc.

There are no functional changes in this patch.

Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/misc/mei/bus.c

index 18c37af22f07349450d7e6c6e586fe6f37db4b1d..6ea8a408f4773a68f6b1b1727a78649fad50c920 100644 (file)
 #define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver)
 #define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev)
 
-static int mei_cl_device_match(struct device *dev, struct device_driver *drv)
+/**
+ * __mei_cl_send - internal client send (write)
+ *
+ * @cl: host client
+ * @buf: buffer to send
+ * @length: buffer length
+ * @blocking: wait for write completion
+ *
+ * Return: written size bytes or < 0 on error
+ */
+ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
+                       bool blocking)
 {
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       struct mei_cl_driver *cldrv = to_mei_cl_driver(drv);
-       const struct mei_cl_device_id *id;
-       const uuid_le *uuid;
-       const char *name;
-
-       if (!cldev)
-               return 0;
+       struct mei_device *bus;
+       struct mei_cl_cb *cb = NULL;
+       ssize_t rets;
 
-       uuid = mei_me_cl_uuid(cldev->me_cl);
-       name = cldev->name;
+       if (WARN_ON(!cl || !cl->dev))
+               return -ENODEV;
 
-       if (!cldrv || !cldrv->id_table)
-               return 0;
+       bus = cl->dev;
 
-       id = cldrv->id_table;
+       mutex_lock(&bus->device_lock);
+       if (!mei_cl_is_connected(cl)) {
+               rets = -ENODEV;
+               goto out;
+       }
 
-       while (uuid_le_cmp(NULL_UUID_LE, id->uuid)) {
+       /* Check if we have an ME client device */
+       if (!mei_me_cl_is_active(cl->me_cl)) {
+               rets = -ENOTTY;
+               goto out;
+       }
 
-               if (!uuid_le_cmp(*uuid, id->uuid)) {
-                       if (id->name[0]) {
-                               if (!strncmp(name, id->name, sizeof(id->name)))
-                                       return 1;
-                       } else {
-                               return 1;
-                       }
-               }
+       if (length > mei_cl_mtu(cl)) {
+               rets = -EFBIG;
+               goto out;
+       }
 
-               id++;
+       cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL);
+       if (!cb) {
+               rets = -ENOMEM;
+               goto out;
        }
 
-       return 0;
+       memcpy(cb->buf.data, buf, length);
+
+       rets = mei_cl_write(cl, cb, blocking);
+
+out:
+       mutex_unlock(&bus->device_lock);
+       if (rets < 0)
+               mei_io_cb_free(cb);
+
+       return rets;
 }
 
-static int mei_cl_device_probe(struct device *dev)
+/**
+ * __mei_cl_recv - internal client receive (read)
+ *
+ * @cl: host client
+ * @buf: buffer to send
+ * @length: buffer length
+ *
+ * Return: read size in bytes of < 0 on error
+ */
+ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length)
 {
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       struct mei_cl_driver *cldrv;
-       struct mei_cl_device_id id;
-
-       if (!cldev)
-               return 0;
+       struct mei_device *bus;
+       struct mei_cl_cb *cb;
+       size_t r_length;
+       ssize_t rets;
 
-       cldrv = to_mei_cl_driver(dev->driver);
-       if (!cldrv || !cldrv->probe)
+       if (WARN_ON(!cl || !cl->dev))
                return -ENODEV;
 
-       dev_dbg(dev, "Device probe\n");
+       bus = cl->dev;
 
-       strlcpy(id.name, cldev->name, sizeof(id.name));
+       mutex_lock(&bus->device_lock);
 
-       return cldrv->probe(cldev, &id);
-}
+       cb = mei_cl_read_cb(cl, NULL);
+       if (cb)
+               goto copy;
 
-static int mei_cl_device_remove(struct device *dev)
-{
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       struct mei_cl_driver *cldrv;
+       rets = mei_cl_read_start(cl, length, NULL);
+       if (rets && rets != -EBUSY)
+               goto out;
 
-       if (!cldev || !dev->driver)
-               return 0;
+       /* wait on event only if there is no other waiter */
+       if (list_empty(&cl->rd_completed) && !waitqueue_active(&cl->rx_wait)) {
 
-       if (cldev->event_cb) {
-               cldev->event_cb = NULL;
-               cancel_work_sync(&cldev->event_work);
-       }
+               mutex_unlock(&bus->device_lock);
 
-       cldrv = to_mei_cl_driver(dev->driver);
-       if (!cldrv->remove) {
-               dev->driver = NULL;
+               if (wait_event_interruptible(cl->rx_wait,
+                               (!list_empty(&cl->rd_completed)) ||
+                               (!mei_cl_is_connected(cl)))) {
 
-               return 0;
+                       if (signal_pending(current))
+                               return -EINTR;
+                       return -ERESTARTSYS;
+               }
+
+               mutex_lock(&bus->device_lock);
+
+               if (!mei_cl_is_connected(cl)) {
+                       rets = -EBUSY;
+                       goto out;
+               }
        }
 
-       return cldrv->remove(cldev);
-}
+       cb = mei_cl_read_cb(cl, NULL);
+       if (!cb) {
+               rets = 0;
+               goto out;
+       }
 
-static ssize_t name_show(struct device *dev, struct device_attribute *a,
-                            char *buf)
-{
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       size_t len;
+copy:
+       if (cb->status) {
+               rets = cb->status;
+               goto free;
+       }
 
-       len = snprintf(buf, PAGE_SIZE, "%s", cldev->name);
+       r_length = min_t(size_t, length, cb->buf_idx);
+       memcpy(buf, cb->buf.data, r_length);
+       rets = r_length;
 
-       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+free:
+       mei_io_cb_free(cb);
+out:
+       mutex_unlock(&bus->device_lock);
+
+       return rets;
 }
-static DEVICE_ATTR_RO(name);
 
-static ssize_t uuid_show(struct device *dev, struct device_attribute *a,
-                            char *buf)
+/**
+ * mei_cl_send - me device send  (write)
+ *
+ * @cldev: me client device
+ * @buf: buffer to send
+ * @length: buffer length
+ *
+ * Return: written size in bytes or < 0 on error
+ */
+ssize_t mei_cl_send(struct mei_cl_device *cldev, u8 *buf, size_t length)
 {
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
-       size_t len;
+       struct mei_cl *cl = cldev->cl;
 
-       len = snprintf(buf, PAGE_SIZE, "%pUl", uuid);
+       if (cl == NULL)
+               return -ENODEV;
 
-       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+       return __mei_cl_send(cl, buf, length, 1);
 }
-static DEVICE_ATTR_RO(uuid);
+EXPORT_SYMBOL_GPL(mei_cl_send);
 
-static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
-                            char *buf)
+/**
+ * mei_cl_recv - client receive (read)
+ *
+ * @cldev: me client device
+ * @buf: buffer to send
+ * @length: buffer length
+ *
+ * Return: read size in bytes of < 0 on error
+ */
+ssize_t mei_cl_recv(struct mei_cl_device *cldev, u8 *buf, size_t length)
 {
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
-       size_t len;
+       struct mei_cl *cl = cldev->cl;
 
-       len = snprintf(buf, PAGE_SIZE, "mei:%s:" MEI_CL_UUID_FMT ":",
-               cldev->name, MEI_CL_UUID_ARGS(uuid->b));
+       if (cl == NULL)
+               return -ENODEV;
 
-       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+       return __mei_cl_recv(cl, buf, length);
 }
-static DEVICE_ATTR_RO(modalias);
-
-static struct attribute *mei_cl_dev_attrs[] = {
-       &dev_attr_name.attr,
-       &dev_attr_uuid.attr,
-       &dev_attr_modalias.attr,
-       NULL,
-};
-ATTRIBUTE_GROUPS(mei_cl_dev);
+EXPORT_SYMBOL_GPL(mei_cl_recv);
 
-static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env)
+/**
+ * mei_bus_event_work  - dispatch rx event for a bus device
+ *    and schedule new work
+ *
+ * @work: work
+ */
+static void mei_bus_event_work(struct work_struct *work)
 {
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
-       const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
+       struct mei_cl_device *cldev;
 
-       if (add_uevent_var(env, "MEI_CL_UUID=%pUl", uuid))
-               return -ENOMEM;
+       cldev = container_of(work, struct mei_cl_device, event_work);
 
-       if (add_uevent_var(env, "MEI_CL_NAME=%s", cldev->name))
-               return -ENOMEM;
+       if (cldev->event_cb)
+               cldev->event_cb(cldev, cldev->events, cldev->event_context);
 
-       if (add_uevent_var(env, "MODALIAS=mei:%s:" MEI_CL_UUID_FMT ":",
-               cldev->name, MEI_CL_UUID_ARGS(uuid->b)))
-               return -ENOMEM;
+       cldev->events = 0;
 
-       return 0;
+       /* Prepare for the next read */
+       mei_cl_read_start(cldev->cl, 0, NULL);
 }
 
-static struct bus_type mei_cl_bus_type = {
-       .name           = "mei",
-       .dev_groups     = mei_cl_dev_groups,
-       .match          = mei_cl_device_match,
-       .probe          = mei_cl_device_probe,
-       .remove         = mei_cl_device_remove,
-       .uevent         = mei_cl_uevent,
-};
-
-static void mei_cl_dev_release(struct device *dev)
+/**
+ * mei_cl_bus_rx_event  - schedule rx evenet
+ *
+ * @cl: host client
+ */
+void mei_cl_bus_rx_event(struct mei_cl *cl)
 {
-       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       struct mei_cl_device *cldev = cl->cldev;
 
-       if (!cldev)
+       if (!cldev || !cldev->event_cb)
                return;
 
-       mei_me_cl_put(cldev->me_cl);
-       kfree(cldev);
-}
-
-static struct device_type mei_cl_device_type = {
-       .release        = mei_cl_dev_release,
-};
-
-struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *bus,
-                                        uuid_le uuid)
-{
-       struct mei_cl *cl;
-
-       list_for_each_entry(cl, &bus->device_list, device_link) {
-               if (cl->cldev && cl->cldev->me_cl &&
-                   !uuid_le_cmp(uuid, *mei_me_cl_uuid(cl->cldev->me_cl)))
-                       return cl;
-       }
+       set_bit(MEI_CL_EVENT_RX, &cldev->events);
 
-       return NULL;
+       schedule_work(&cldev->event_work);
 }
 
-struct mei_cl_device *mei_cl_add_device(struct mei_device *bus,
-                                       struct mei_me_client *me_cl,
-                                       struct mei_cl *cl,
-                                       char *name)
+/**
+ * mei_cl_register_event_cb - register event callback
+ *
+ * @cldev: me client devices
+ * @event_cb: callback function
+ * @context: driver context data
+ *
+ * Return: 0 on success
+ *         -EALREADY if an callback is already registered
+ *         <0 on other errors
+ */
+int mei_cl_register_event_cb(struct mei_cl_device *cldev,
+                         mei_cl_event_cb_t event_cb, void *context)
 {
-       struct mei_cl_device *cldev;
-       int status;
-
-       cldev = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL);
-       if (!cldev)
-               return NULL;
-
-       cldev->me_cl = mei_me_cl_get(me_cl);
-       if (!cldev->me_cl) {
-               kfree(cldev);
-               return NULL;
-       }
-
-       cldev->cl = cl;
-       cldev->dev.parent = bus->dev;
-       cldev->dev.bus = &mei_cl_bus_type;
-       cldev->dev.type = &mei_cl_device_type;
-
-       strlcpy(cldev->name, name, sizeof(cldev->name));
-
-       dev_set_name(&cldev->dev, "mei:%s:%pUl", name, mei_me_cl_uuid(me_cl));
+       if (cldev->event_cb)
+               return -EALREADY;
 
-       status = device_register(&cldev->dev);
-       if (status) {
-               dev_err(bus->dev, "Failed to register MEI device\n");
-               mei_me_cl_put(cldev->me_cl);
-               kfree(cldev);
-               return NULL;
-       }
+       cldev->events = 0;
+       cldev->event_cb = event_cb;
+       cldev->event_context = context;
+       INIT_WORK(&cldev->event_work, mei_bus_event_work);
 
-       cl->cldev = cldev;
+       mei_cl_read_start(cldev->cl, 0, NULL);
 
-       dev_dbg(&cldev->dev, "client %s registered\n", name);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(mei_cl_register_event_cb);
 
-       return cldev;
+/**
+ * mei_cl_get_drvdata - driver data getter
+ *
+ * @cldev: mei client device
+ *
+ * Return: driver private data
+ */
+void *mei_cl_get_drvdata(const struct mei_cl_device *cldev)
+{
+       return dev_get_drvdata(&cldev->dev);
 }
-EXPORT_SYMBOL_GPL(mei_cl_add_device);
+EXPORT_SYMBOL_GPL(mei_cl_get_drvdata);
 
-void mei_cl_remove_device(struct mei_cl_device *cldev)
+/**
+ * mei_cl_set_drvdata - driver data setter
+ *
+ * @cldev: mei client device
+ * @data: data to store
+ */
+void mei_cl_set_drvdata(struct mei_cl_device *cldev, void *data)
 {
-       device_unregister(&cldev->dev);
+       dev_set_drvdata(&cldev->dev, data);
 }
-EXPORT_SYMBOL_GPL(mei_cl_remove_device);
+EXPORT_SYMBOL_GPL(mei_cl_set_drvdata);
 
-int __mei_cl_driver_register(struct mei_cl_driver *cldrv, struct module *owner)
+/**
+ * mei_cl_enable_device - enable me client device
+ *     create connection with me client
+ *
+ * @cldev: me client device
+ *
+ * Return: 0 on success and < 0 on error
+ */
+int mei_cl_enable_device(struct mei_cl_device *cldev)
 {
        int err;
+       struct mei_device *bus;
+       struct mei_cl *cl = cldev->cl;
 
-       cldrv->driver.name = cldrv->name;
-       cldrv->driver.owner = owner;
-       cldrv->driver.bus = &mei_cl_bus_type;
+       if (cl == NULL)
+               return -ENODEV;
 
-       err = driver_register(&cldrv->driver);
-       if (err)
-               return err;
+       bus = cl->dev;
 
-       pr_debug("mei: driver [%s] registered\n", cldrv->driver.name);
+       mutex_lock(&bus->device_lock);
 
-       return 0;
-}
-EXPORT_SYMBOL_GPL(__mei_cl_driver_register);
+       if (mei_cl_is_connected(cl)) {
+               mutex_unlock(&bus->device_lock);
+               dev_warn(bus->dev, "Already connected");
+               return -EBUSY;
+       }
 
-void mei_cl_driver_unregister(struct mei_cl_driver *cldrv)
-{
-       driver_unregister(&cldrv->driver);
+       err = mei_cl_connect(cl, cldev->me_cl, NULL);
+       if (err < 0) {
+               mutex_unlock(&bus->device_lock);
+               dev_err(bus->dev, "Could not connect to the ME client");
 
-       pr_debug("mei: driver [%s] unregistered\n", cldrv->driver.name);
+               return err;
+       }
+
+       mutex_unlock(&bus->device_lock);
+
+       if (cldev->event_cb)
+               mei_cl_read_start(cldev->cl, 0, NULL);
+
+       return 0;
 }
-EXPORT_SYMBOL_GPL(mei_cl_driver_unregister);
+EXPORT_SYMBOL_GPL(mei_cl_enable_device);
 
-ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
-                       bool blocking)
+/**
+ * mei_cl_disable_device - disable me client device
+ *     disconnect form the me client
+ *
+ * @cldev: me client device
+ *
+ * Return: 0 on success and < 0 on error
+ */
+int mei_cl_disable_device(struct mei_cl_device *cldev)
 {
+       int err;
        struct mei_device *bus;
-       struct mei_cl_cb *cb = NULL;
-       ssize_t rets;
+       struct mei_cl *cl = cldev->cl;
 
-       if (WARN_ON(!cl || !cl->dev))
+       if (cl == NULL)
                return -ENODEV;
 
        bus = cl->dev;
 
-       mutex_lock(&bus->device_lock);
-       if (!mei_cl_is_connected(cl)) {
-               rets = -ENODEV;
-               goto out;
-       }
+       cldev->event_cb = NULL;
 
-       /* Check if we have an ME client device */
-       if (!mei_me_cl_is_active(cl->me_cl)) {
-               rets = -ENOTTY;
-               goto out;
-       }
+       mutex_lock(&bus->device_lock);
 
-       if (length > mei_cl_mtu(cl)) {
-               rets = -EFBIG;
+       if (!mei_cl_is_connected(cl)) {
+               dev_err(bus->dev, "Already disconnected");
+               err = 0;
                goto out;
        }
 
-       cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL);
-       if (!cb) {
-               rets = -ENOMEM;
+       err = mei_cl_disconnect(cl);
+       if (err < 0) {
+               dev_err(bus->dev, "Could not disconnect from the ME client");
                goto out;
        }
 
-       memcpy(cb->buf.data, buf, length);
-
-       rets = mei_cl_write(cl, cb, blocking);
+       /* Flush queues and remove any pending read */
+       mei_cl_flush_queues(cl, NULL);
 
 out:
        mutex_unlock(&bus->device_lock);
-       if (rets < 0)
-               mei_io_cb_free(cb);
+       return err;
 
-       return rets;
 }
+EXPORT_SYMBOL_GPL(mei_cl_disable_device);
 
-ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length)
+static int mei_cl_device_match(struct device *dev, struct device_driver *drv)
 {
-       struct mei_device *bus;
-       struct mei_cl_cb *cb;
-       size_t r_length;
-       ssize_t rets;
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       struct mei_cl_driver *cldrv = to_mei_cl_driver(drv);
+       const struct mei_cl_device_id *id;
+       const uuid_le *uuid;
+       const char *name;
 
-       if (WARN_ON(!cl || !cl->dev))
-               return -ENODEV;
+       if (!cldev)
+               return 0;
 
-       bus = cl->dev;
+       uuid = mei_me_cl_uuid(cldev->me_cl);
+       name = cldev->name;
 
-       mutex_lock(&bus->device_lock);
+       if (!cldrv || !cldrv->id_table)
+               return 0;
 
-       cb = mei_cl_read_cb(cl, NULL);
-       if (cb)
-               goto copy;
+       id = cldrv->id_table;
 
-       rets = mei_cl_read_start(cl, length, NULL);
-       if (rets && rets != -EBUSY)
-               goto out;
+       while (uuid_le_cmp(NULL_UUID_LE, id->uuid)) {
 
-       if (list_empty(&cl->rd_completed) && !waitqueue_active(&cl->rx_wait)) {
+               if (!uuid_le_cmp(*uuid, id->uuid)) {
+                       if (id->name[0]) {
+                               if (!strncmp(name, id->name, sizeof(id->name)))
+                                       return 1;
+                       } else {
+                               return 1;
+                       }
+               }
 
-               mutex_unlock(&bus->device_lock);
+               id++;
+       }
 
-               if (wait_event_interruptible(cl->rx_wait,
-                               (!list_empty(&cl->rd_completed)) ||
-                               (!mei_cl_is_connected(cl)))) {
+       return 0;
+}
 
-                       if (signal_pending(current))
-                               return -EINTR;
-                       return -ERESTARTSYS;
-               }
+static int mei_cl_device_probe(struct device *dev)
+{
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       struct mei_cl_driver *cldrv;
+       struct mei_cl_device_id id;
 
-               mutex_lock(&bus->device_lock);
+       if (!cldev)
+               return 0;
 
-               if (!mei_cl_is_connected(cl)) {
-                       rets = -EBUSY;
-                       goto out;
-               }
-       }
+       cldrv = to_mei_cl_driver(dev->driver);
+       if (!cldrv || !cldrv->probe)
+               return -ENODEV;
+
+       dev_dbg(dev, "Device probe\n");
+
+       strlcpy(id.name, cldev->name, sizeof(id.name));
+
+       return cldrv->probe(cldev, &id);
+}
 
-       cb = mei_cl_read_cb(cl, NULL);
-       if (!cb) {
-               rets = 0;
-               goto out;
-       }
+static int mei_cl_device_remove(struct device *dev)
+{
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       struct mei_cl_driver *cldrv;
 
-copy:
-       if (cb->status) {
-               rets = cb->status;
-               goto free;
+       if (!cldev || !dev->driver)
+               return 0;
+
+       if (cldev->event_cb) {
+               cldev->event_cb = NULL;
+               cancel_work_sync(&cldev->event_work);
        }
 
-       r_length = min_t(size_t, length, cb->buf_idx);
-       memcpy(buf, cb->buf.data, r_length);
-       rets = r_length;
+       cldrv = to_mei_cl_driver(dev->driver);
+       if (!cldrv->remove) {
+               dev->driver = NULL;
 
-free:
-       mei_io_cb_free(cb);
-out:
-       mutex_unlock(&bus->device_lock);
+               return 0;
+       }
 
-       return rets;
+       return cldrv->remove(cldev);
 }
 
-ssize_t mei_cl_send(struct mei_cl_device *cldev, u8 *buf, size_t length)
+static ssize_t name_show(struct device *dev, struct device_attribute *a,
+                            char *buf)
 {
-       struct mei_cl *cl = cldev->cl;
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       size_t len;
 
-       if (cl == NULL)
-               return -ENODEV;
+       len = snprintf(buf, PAGE_SIZE, "%s", cldev->name);
 
-       return __mei_cl_send(cl, buf, length, 1);
+       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
 }
-EXPORT_SYMBOL_GPL(mei_cl_send);
+static DEVICE_ATTR_RO(name);
 
-ssize_t mei_cl_recv(struct mei_cl_device *cldev, u8 *buf, size_t length)
+static ssize_t uuid_show(struct device *dev, struct device_attribute *a,
+                            char *buf)
 {
-       struct mei_cl *cl = cldev->cl;
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
+       size_t len;
 
-       if (cl == NULL)
-               return -ENODEV;
+       len = snprintf(buf, PAGE_SIZE, "%pUl", uuid);
 
-       return __mei_cl_recv(cl, buf, length);
+       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
 }
-EXPORT_SYMBOL_GPL(mei_cl_recv);
+static DEVICE_ATTR_RO(uuid);
 
-static void mei_bus_event_work(struct work_struct *work)
+static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
+                            char *buf)
 {
-       struct mei_cl_device *cldev;
-
-       cldev = container_of(work, struct mei_cl_device, event_work);
-
-       if (cldev->event_cb)
-               cldev->event_cb(cldev, cldev->events, cldev->event_context);
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
+       size_t len;
 
-       cldev->events = 0;
+       len = snprintf(buf, PAGE_SIZE, "mei:%s:" MEI_CL_UUID_FMT ":",
+               cldev->name, MEI_CL_UUID_ARGS(uuid->b));
 
-       /* Prepare for the next read */
-       mei_cl_read_start(cldev->cl, 0, NULL);
+       return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
 }
+static DEVICE_ATTR_RO(modalias);
 
-int mei_cl_register_event_cb(struct mei_cl_device *cldev,
-                         mei_cl_event_cb_t event_cb, void *context)
+static struct attribute *mei_cl_dev_attrs[] = {
+       &dev_attr_name.attr,
+       &dev_attr_uuid.attr,
+       &dev_attr_modalias.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(mei_cl_dev);
+
+static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env)
 {
-       if (cldev->event_cb)
-               return -EALREADY;
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+       const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
 
-       cldev->events = 0;
-       cldev->event_cb = event_cb;
-       cldev->event_context = context;
-       INIT_WORK(&cldev->event_work, mei_bus_event_work);
+       if (add_uevent_var(env, "MEI_CL_UUID=%pUl", uuid))
+               return -ENOMEM;
 
-       mei_cl_read_start(cldev->cl, 0, NULL);
+       if (add_uevent_var(env, "MEI_CL_NAME=%s", cldev->name))
+               return -ENOMEM;
+
+       if (add_uevent_var(env, "MODALIAS=mei:%s:" MEI_CL_UUID_FMT ":",
+               cldev->name, MEI_CL_UUID_ARGS(uuid->b)))
+               return -ENOMEM;
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(mei_cl_register_event_cb);
 
-void *mei_cl_get_drvdata(const struct mei_cl_device *cldev)
+static struct bus_type mei_cl_bus_type = {
+       .name           = "mei",
+       .dev_groups     = mei_cl_dev_groups,
+       .match          = mei_cl_device_match,
+       .probe          = mei_cl_device_probe,
+       .remove         = mei_cl_device_remove,
+       .uevent         = mei_cl_uevent,
+};
+
+static void mei_cl_dev_release(struct device *dev)
 {
-       return dev_get_drvdata(&cldev->dev);
+       struct mei_cl_device *cldev = to_mei_cl_device(dev);
+
+       if (!cldev)
+               return;
+
+       mei_me_cl_put(cldev->me_cl);
+       kfree(cldev);
 }
-EXPORT_SYMBOL_GPL(mei_cl_get_drvdata);
 
-void mei_cl_set_drvdata(struct mei_cl_device *cldev, void *data)
+static struct device_type mei_cl_device_type = {
+       .release        = mei_cl_dev_release,
+};
+
+struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *bus,
+                                        uuid_le uuid)
 {
-       dev_set_drvdata(&cldev->dev, data);
+       struct mei_cl *cl;
+
+       list_for_each_entry(cl, &bus->device_list, device_link) {
+               if (cl->cldev && cl->cldev->me_cl &&
+                   !uuid_le_cmp(uuid, *mei_me_cl_uuid(cl->cldev->me_cl)))
+                       return cl;
+       }
+
+       return NULL;
 }
-EXPORT_SYMBOL_GPL(mei_cl_set_drvdata);
 
-int mei_cl_enable_device(struct mei_cl_device *cldev)
+struct mei_cl_device *mei_cl_add_device(struct mei_device *bus,
+                                       struct mei_me_client *me_cl,
+                                       struct mei_cl *cl,
+                                       char *name)
 {
-       int err;
-       struct mei_device *bus;
-       struct mei_cl *cl = cldev->cl;
+       struct mei_cl_device *cldev;
+       int status;
 
-       if (cl == NULL)
-               return -ENODEV;
+       cldev = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL);
+       if (!cldev)
+               return NULL;
 
-       bus = cl->dev;
+       cldev->me_cl = mei_me_cl_get(me_cl);
+       if (!cldev->me_cl) {
+               kfree(cldev);
+               return NULL;
+       }
 
-       mutex_lock(&bus->device_lock);
+       cldev->cl = cl;
+       cldev->dev.parent = bus->dev;
+       cldev->dev.bus = &mei_cl_bus_type;
+       cldev->dev.type = &mei_cl_device_type;
 
-       if (mei_cl_is_connected(cl)) {
-               mutex_unlock(&bus->device_lock);
-               dev_warn(bus->dev, "Already connected");
-               return -EBUSY;
-       }
+       strlcpy(cldev->name, name, sizeof(cldev->name));
 
-       err = mei_cl_connect(cl, cldev->me_cl, NULL);
-       if (err < 0) {
-               mutex_unlock(&bus->device_lock);
-               dev_err(bus->dev, "Could not connect to the ME client");
+       dev_set_name(&cldev->dev, "mei:%s:%pUl", name, mei_me_cl_uuid(me_cl));
 
-               return err;
+       status = device_register(&cldev->dev);
+       if (status) {
+               dev_err(bus->dev, "Failed to register MEI device\n");
+               mei_me_cl_put(cldev->me_cl);
+               kfree(cldev);
+               return NULL;
        }
 
-       mutex_unlock(&bus->device_lock);
+       cl->cldev = cldev;
 
-       if (cldev->event_cb)
-               mei_cl_read_start(cldev->cl, 0, NULL);
+       dev_dbg(&cldev->dev, "client %s registered\n", name);
 
-       return 0;
+       return cldev;
 }
-EXPORT_SYMBOL_GPL(mei_cl_enable_device);
+EXPORT_SYMBOL_GPL(mei_cl_add_device);
 
-int mei_cl_disable_device(struct mei_cl_device *cldev)
+void mei_cl_remove_device(struct mei_cl_device *cldev)
 {
-       int err;
-       struct mei_device *bus;
-       struct mei_cl *cl = cldev->cl;
-
-       if (cl == NULL)
-               return -ENODEV;
-
-       bus = cl->dev;
-
-       cldev->event_cb = NULL;
-
-       mutex_lock(&bus->device_lock);
+       device_unregister(&cldev->dev);
+}
+EXPORT_SYMBOL_GPL(mei_cl_remove_device);
 
-       if (!mei_cl_is_connected(cl)) {
-               dev_err(bus->dev, "Already disconnected");
-               err = 0;
-               goto out;
-       }
+int __mei_cl_driver_register(struct mei_cl_driver *cldrv, struct module *owner)
+{
+       int err;
 
-       err = mei_cl_disconnect(cl);
-       if (err < 0) {
-               dev_err(bus->dev, "Could not disconnect from the ME client");
-               goto out;
-       }
+       cldrv->driver.name = cldrv->name;
+       cldrv->driver.owner = owner;
+       cldrv->driver.bus = &mei_cl_bus_type;
 
-       /* Flush queues and remove any pending read */
-       mei_cl_flush_queues(cl, NULL);
+       err = driver_register(&cldrv->driver);
+       if (err)
+               return err;
 
-out:
-       mutex_unlock(&bus->device_lock);
-       return err;
+       pr_debug("mei: driver [%s] registered\n", cldrv->driver.name);
 
+       return 0;
 }
-EXPORT_SYMBOL_GPL(mei_cl_disable_device);
+EXPORT_SYMBOL_GPL(__mei_cl_driver_register);
 
-void mei_cl_bus_rx_event(struct mei_cl *cl)
+void mei_cl_driver_unregister(struct mei_cl_driver *cldrv)
 {
-       struct mei_cl_device *cldev = cl->cldev;
-
-       if (!cldev || !cldev->event_cb)
-               return;
-
-       set_bit(MEI_CL_EVENT_RX, &cldev->events);
+       driver_unregister(&cldrv->driver);
 
-       schedule_work(&cldev->event_work);
+       pr_debug("mei: driver [%s] unregistered\n", cldrv->driver.name);
 }
+EXPORT_SYMBOL_GPL(mei_cl_driver_unregister);
 
 int __init mei_cl_bus_init(void)
 {