mei: fix double freeing of a cb during link reset
[cascardo/linux.git] / drivers / misc / mei / client.c
index c57ac25..a9cdb92 100644 (file)
@@ -536,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);
 
@@ -588,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;
@@ -671,25 +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);
-
        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");
@@ -720,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
@@ -735,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;
 
@@ -1114,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;
@@ -1129,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;
 
@@ -1144,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.
  */
-static 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;
 
@@ -1159,7 +1171,7 @@ static 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;
@@ -1364,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);
 }
 
 /**
@@ -1539,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;
 
@@ -1633,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;
@@ -1645,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;
 
@@ -1692,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) {
@@ -1700,6 +1713,10 @@ out:
                                rets = -EINTR;
                        goto err;
                }
+               if (cl->writing_state != MEI_WRITE_COMPLETE) {
+                       rets = -EFAULT;
+                       goto err;
+               }
        }
 
        rets = size;
@@ -1707,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;
 }
@@ -1736,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:
@@ -1768,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);
-}
-
-