mei: fix double freeing of a cb during link reset
[cascardo/linux.git] / drivers / misc / mei / client.c
index a6c87c7..a9cdb92 100644 (file)
@@ -359,7 +359,7 @@ void mei_io_cb_free(struct mei_cl_cb *cb)
  * Return: mei_cl_cb pointer or NULL;
  */
 struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
-                                struct file *fp)
+                                const struct file *fp)
 {
        struct mei_cl_cb *cb;
 
@@ -368,7 +368,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
                return NULL;
 
        INIT_LIST_HEAD(&cb->list);
-       cb->file_object = fp;
+       cb->fp = fp;
        cb->cl = cl;
        cb->buf_idx = 0;
        cb->fop_type = type;
@@ -455,7 +455,8 @@ int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length)
  * Return: cb on success and NULL on failure
  */
 struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
-                                 enum mei_cb_file_ops type, struct file *fp)
+                                 enum mei_cb_file_ops type,
+                                 const struct file *fp)
 {
        struct mei_cl_cb *cb;
 
@@ -485,7 +486,7 @@ struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp)
        struct mei_cl_cb *cb;
 
        list_for_each_entry(cb, &cl->rd_completed, list)
-               if (!fp || fp == cb->file_object)
+               if (!fp || fp == cb->fp)
                        return cb;
 
        return NULL;
@@ -503,12 +504,12 @@ void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp)
        struct mei_cl_cb *cb, *next;
 
        list_for_each_entry_safe(cb, next, &cl->rd_completed, list)
-               if (!fp || fp == cb->file_object)
+               if (!fp || fp == cb->fp)
                        mei_io_cb_free(cb);
 
 
        list_for_each_entry_safe(cb, next, &cl->rd_pending, list)
-               if (!fp || fp == cb->file_object)
+               if (!fp || fp == cb->fp)
                        mei_io_cb_free(cb);
 }
 
@@ -535,7 +536,6 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
        mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);
        mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);
        mei_io_list_flush(&cl->dev->amthif_cmd_list, cl);
-       mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl);
 
        mei_cl_read_cb_flush(cl, fp);
 
@@ -587,27 +587,23 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev)
  * mei_cl_link - allocate host id in the host map
  *
  * @cl: host client
- * @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one
  *
  * Return: 0 on success
  *     -EINVAL on incorrect values
  *     -EMFILE if open count exceeded.
  */
-int mei_cl_link(struct mei_cl *cl, int id)
+int mei_cl_link(struct mei_cl *cl)
 {
        struct mei_device *dev;
        long open_handle_count;
+       int id;
 
        if (WARN_ON(!cl || !cl->dev))
                return -EINVAL;
 
        dev = cl->dev;
 
-       /* If Id is not assigned get one*/
-       if (id == MEI_HOST_CLIENT_ID_ANY)
-               id = find_first_zero_bit(dev->host_clients_map,
-                                       MEI_CLIENTS_MAX);
-
+       id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX);
        if (id >= MEI_CLIENTS_MAX) {
                dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX);
                return -EMFILE;
@@ -648,7 +644,7 @@ int mei_cl_unlink(struct mei_cl *cl)
        if (!cl)
                return 0;
 
-       /* wd and amthif might not be initialized */
+       /* amthif might not be initialized */
        if (!cl->dev)
                return 0;
 
@@ -670,31 +666,12 @@ int mei_cl_unlink(struct mei_cl *cl)
        return 0;
 }
 
-
-void mei_host_client_init(struct work_struct *work)
+void mei_host_client_init(struct mei_device *dev)
 {
-       struct mei_device *dev =
-               container_of(work, struct mei_device, init_work);
-       struct mei_me_client *me_cl;
-
-       mutex_lock(&dev->device_lock);
-
-
-       me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
-       if (me_cl)
-               mei_amthif_host_init(dev, me_cl);
-       mei_me_cl_put(me_cl);
-
-       me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid);
-       if (me_cl)
-               mei_wd_host_init(dev, me_cl);
-       mei_me_cl_put(me_cl);
-
        dev->dev_state = MEI_DEV_ENABLED;
        dev->reset_count = 0;
-       mutex_unlock(&dev->device_lock);
 
-       mei_cl_bus_rescan(dev);
+       schedule_work(&dev->bus_rescan_work);
 
        pm_runtime_mark_last_busy(dev->dev);
        dev_dbg(dev->dev, "rpm: autosuspend\n");
@@ -725,6 +702,33 @@ bool mei_hbuf_acquire(struct mei_device *dev)
        return true;
 }
 
+/**
+ * mei_cl_wake_all - wake up readers, writers and event waiters so
+ *                 they can be interrupted
+ *
+ * @cl: host client
+ */
+static void mei_cl_wake_all(struct mei_cl *cl)
+{
+       struct mei_device *dev = cl->dev;
+
+       /* synchronized under device mutex */
+       if (waitqueue_active(&cl->rx_wait)) {
+               cl_dbg(dev, cl, "Waking up reading client!\n");
+               wake_up_interruptible(&cl->rx_wait);
+       }
+       /* synchronized under device mutex */
+       if (waitqueue_active(&cl->tx_wait)) {
+               cl_dbg(dev, cl, "Waking up writing client!\n");
+               wake_up_interruptible(&cl->tx_wait);
+       }
+       /* synchronized under device mutex */
+       if (waitqueue_active(&cl->ev_wait)) {
+               cl_dbg(dev, cl, "Waking up waiting for event clients!\n");
+               wake_up_interruptible(&cl->ev_wait);
+       }
+}
+
 /**
  * mei_cl_set_disconnected - set disconnected state and clear
  *   associated states and resources
@@ -740,8 +744,11 @@ void mei_cl_set_disconnected(struct mei_cl *cl)
                return;
 
        cl->state = MEI_FILE_DISCONNECTED;
+       mei_io_list_free(&dev->write_list, cl);
+       mei_io_list_free(&dev->write_waiting_list, cl);
        mei_io_list_flush(&dev->ctrl_rd_list, cl);
        mei_io_list_flush(&dev->ctrl_wr_list, cl);
+       mei_cl_wake_all(cl);
        cl->mei_flow_ctrl_creds = 0;
        cl->timer_count = 0;
 
@@ -1034,7 +1041,7 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
  * Return: 0 on success, <0 on failure.
  */
 int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
-                  struct file *file)
+                 const struct file *file)
 {
        struct mei_device *dev;
        struct mei_cl_cb *cb;
@@ -1119,11 +1126,10 @@ nortpm:
  * mei_cl_alloc_linked - allocate and link host client
  *
  * @dev: the device structure
- * @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one
  *
  * Return: cl on success ERR_PTR on failure
  */
-struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)
+struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev)
 {
        struct mei_cl *cl;
        int ret;
@@ -1134,7 +1140,7 @@ struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)
                goto err;
        }
 
-       ret = mei_cl_link(cl, id);
+       ret = mei_cl_link(cl);
        if (ret)
                goto err;
 
@@ -1149,11 +1155,12 @@ err:
 /**
  * mei_cl_flow_ctrl_creds - checks flow_control credits for cl.
  *
- * @cl: private data of the file object
+ * @cl: host client
+ * @fp: the file pointer associated with the pointer
  *
  * Return: 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
  */
-int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
+static int mei_cl_flow_ctrl_creds(struct mei_cl *cl, const struct file *fp)
 {
        int rets;
 
@@ -1164,7 +1171,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
                return 1;
 
        if (mei_cl_is_fixed_address(cl)) {
-               rets = mei_cl_read_start(cl, mei_cl_mtu(cl), NULL);
+               rets = mei_cl_read_start(cl, mei_cl_mtu(cl), fp);
                if (rets && rets != -EBUSY)
                        return rets;
                return 1;
@@ -1186,7 +1193,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
  *     0 on success
  *     -EINVAL when ctrl credits are <= 0
  */
-int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
+static int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
 {
        if (WARN_ON(!cl || !cl->me_cl))
                return -EINVAL;
@@ -1283,7 +1290,8 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
  *
  * Return: 0 on such and error otherwise.
  */
-int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request)
+int mei_cl_notify_request(struct mei_cl *cl,
+                         const struct file *file, u8 request)
 {
        struct mei_device *dev;
        struct mei_cl_cb *cb;
@@ -1368,12 +1376,12 @@ void mei_cl_notify(struct mei_cl *cl)
 
        cl_dbg(dev, cl, "notify event");
        cl->notify_ev = true;
-       wake_up_interruptible_all(&cl->ev_wait);
+       if (!mei_cl_bus_notify_event(cl))
+               wake_up_interruptible(&cl->ev_wait);
 
        if (cl->ev_async)
                kill_fasync(&cl->ev_async, SIGIO, POLL_PRI);
 
-       mei_cl_bus_notify_event(cl);
 }
 
 /**
@@ -1421,6 +1429,25 @@ out:
        return 0;
 }
 
+/**
+ * mei_cl_is_read_fc_cb - check if read cb is waiting for flow control
+ *                        for given host client
+ *
+ * @cl: host client
+ *
+ * Return: true, if found at least one cb.
+ */
+static bool mei_cl_is_read_fc_cb(struct mei_cl *cl)
+{
+       struct mei_device *dev = cl->dev;
+       struct mei_cl_cb *cb;
+
+       list_for_each_entry(cb, &dev->ctrl_wr_list.list, list)
+               if (cb->fop_type == MEI_FOP_READ && cb->cl == cl)
+                       return true;
+       return false;
+}
+
 /**
  * mei_cl_read_start - the start read client message function.
  *
@@ -1430,7 +1457,7 @@ out:
  *
  * Return: 0 on success, <0 on failure.
  */
-int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
+int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
 {
        struct mei_device *dev;
        struct mei_cl_cb *cb;
@@ -1445,7 +1472,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
                return -ENODEV;
 
        /* HW currently supports only one pending read */
-       if (!list_empty(&cl->rd_pending))
+       if (!list_empty(&cl->rd_pending) || mei_cl_is_read_fc_cb(cl))
                return -EBUSY;
 
        if (!mei_me_cl_is_active(cl->me_cl)) {
@@ -1524,7 +1551,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
 
        first_chunk = cb->buf_idx == 0;
 
-       rets = first_chunk ? mei_cl_flow_ctrl_creds(cl) : 1;
+       rets = first_chunk ? mei_cl_flow_ctrl_creds(cl, cb->fp) : 1;
        if (rets < 0)
                return rets;
 
@@ -1556,7 +1583,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
                return 0;
        }
 
-       cl_dbg(dev, cl, "buf: size = %d idx = %lu\n",
+       cl_dbg(dev, cl, "buf: size = %zd idx = %zd\n",
                        cb->buf.size, cb->buf_idx);
 
        rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx);
@@ -1618,7 +1645,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
        if (rets < 0 && rets != -EINPROGRESS) {
                pm_runtime_put_noidle(dev->dev);
                cl_err(dev, cl, "rpm: get failed %d\n", rets);
-               return rets;
+               goto free;
        }
 
        cb->buf_idx = 0;
@@ -1630,7 +1657,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
        mei_hdr.msg_complete = 0;
        mei_hdr.internal = cb->internal;
 
-       rets = mei_cl_flow_ctrl_creds(cl);
+       rets = mei_cl_flow_ctrl_creds(cl, cb->fp);
        if (rets < 0)
                goto err;
 
@@ -1677,7 +1704,8 @@ out:
 
                mutex_unlock(&dev->device_lock);
                rets = wait_event_interruptible(cl->tx_wait,
-                               cl->writing_state == MEI_WRITE_COMPLETE);
+                               cl->writing_state == MEI_WRITE_COMPLETE ||
+                               (!mei_cl_is_connected(cl)));
                mutex_lock(&dev->device_lock);
                /* wait_event_interruptible returns -ERESTARTSYS */
                if (rets) {
@@ -1685,6 +1713,10 @@ out:
                                rets = -EINTR;
                        goto err;
                }
+               if (cl->writing_state != MEI_WRITE_COMPLETE) {
+                       rets = -EFAULT;
+                       goto err;
+               }
        }
 
        rets = size;
@@ -1692,6 +1724,8 @@ err:
        cl_dbg(dev, cl, "rpm: autosuspend\n");
        pm_runtime_mark_last_busy(dev->dev);
        pm_runtime_put_autosuspend(dev->dev);
+free:
+       mei_io_cb_free(cb);
 
        return rets;
 }
@@ -1721,10 +1755,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
 
        case MEI_FOP_READ:
                list_add_tail(&cb->list, &cl->rd_completed);
-               if (waitqueue_active(&cl->rx_wait))
-                       wake_up_interruptible_all(&cl->rx_wait);
-               else
-                       mei_cl_bus_rx_event(cl);
+               if (!mei_cl_bus_rx_event(cl))
+                       wake_up_interruptible(&cl->rx_wait);
                break;
 
        case MEI_FOP_CONNECT:
@@ -1753,44 +1785,3 @@ void mei_cl_all_disconnect(struct mei_device *dev)
        list_for_each_entry(cl, &dev->file_list, link)
                mei_cl_set_disconnected(cl);
 }
-
-
-/**
- * mei_cl_all_wakeup  - wake up all readers and writers they can be interrupted
- *
- * @dev: mei device
- */
-void mei_cl_all_wakeup(struct mei_device *dev)
-{
-       struct mei_cl *cl;
-
-       list_for_each_entry(cl, &dev->file_list, link) {
-               if (waitqueue_active(&cl->rx_wait)) {
-                       cl_dbg(dev, cl, "Waking up reading client!\n");
-                       wake_up_interruptible(&cl->rx_wait);
-               }
-               if (waitqueue_active(&cl->tx_wait)) {
-                       cl_dbg(dev, cl, "Waking up writing client!\n");
-                       wake_up_interruptible(&cl->tx_wait);
-               }
-
-               /* synchronized under device mutex */
-               if (waitqueue_active(&cl->ev_wait)) {
-                       cl_dbg(dev, cl, "Waking up waiting for event clients!\n");
-                       wake_up_interruptible(&cl->ev_wait);
-               }
-       }
-}
-
-/**
- * mei_cl_all_write_clear - clear all pending writes
- *
- * @dev: mei device
- */
-void mei_cl_all_write_clear(struct mei_device *dev)
-{
-       mei_io_list_free(&dev->write_list, NULL);
-       mei_io_list_free(&dev->write_waiting_list, NULL);
-}
-
-