mei: add connect with vtag ioctl
[linux-2.6-block.git] / drivers / misc / mei / main.c
index 401bf8743689a0f2467bd141439bf51a9626af6d..9f6682033ed7eb9bda9759504ff5d6a6b5913727 100644 (file)
@@ -395,17 +395,18 @@ out:
  * mei_ioctl_connect_client - the connect to fw client IOCTL function
  *
  * @file: private data of the file object
- * @data: IOCTL connect data, input and output parameters
+ * @in_client_uuid: requested UUID for connection
+ * @client: IOCTL connect data, output parameters
  *
  * Locking: called under "dev->device_lock" lock
  *
  * Return: 0 on success, <0 on failure.
  */
 static int mei_ioctl_connect_client(struct file *file,
-                       struct mei_connect_client_data *data)
+                                   const uuid_le *in_client_uuid,
+                                   struct mei_client *client)
 {
        struct mei_device *dev;
-       struct mei_client *client;
        struct mei_me_client *me_cl;
        struct mei_cl *cl;
        int rets;
@@ -413,18 +414,15 @@ static int mei_ioctl_connect_client(struct file *file,
        cl = file->private_data;
        dev = cl->dev;
 
-       if (dev->dev_state != MEI_DEV_ENABLED)
-               return -ENODEV;
-
        if (cl->state != MEI_FILE_INITIALIZING &&
            cl->state != MEI_FILE_DISCONNECTED)
                return  -EBUSY;
 
        /* find ME client we're trying to connect to */
-       me_cl = mei_me_cl_by_uuid(dev, &data->in_client_uuid);
+       me_cl = mei_me_cl_by_uuid(dev, in_client_uuid);
        if (!me_cl) {
                dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
-                       &data->in_client_uuid);
+                       in_client_uuid);
                rets = -ENOTTY;
                goto end;
        }
@@ -434,7 +432,7 @@ static int mei_ioctl_connect_client(struct file *file,
                         !dev->allow_fixed_address : !dev->hbm_f_fa_supported;
                if (forbidden) {
                        dev_dbg(dev->dev, "Connection forbidden to FW Client UUID = %pUl\n",
-                               &data->in_client_uuid);
+                               in_client_uuid);
                        rets = -ENOTTY;
                        goto end;
                }
@@ -448,7 +446,6 @@ static int mei_ioctl_connect_client(struct file *file,
                        me_cl->props.max_msg_length);
 
        /* prepare the output buffer */
-       client = &data->out_client_properties;
        client->max_msg_length = me_cl->props.max_msg_length;
        client->protocol_version = me_cl->props.protocol_version;
        dev_dbg(dev->dev, "Can connect?\n");
@@ -460,6 +457,135 @@ end:
        return rets;
 }
 
+/**
+ * mei_vt_support_check - check if client support vtags
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * @dev: mei_device
+ * @uuid: client UUID
+ *
+ * Return:
+ *     0 - supported
+ *     -ENOTTY - no such client
+ *     -EOPNOTSUPP - vtags are not supported by client
+ */
+static int mei_vt_support_check(struct mei_device *dev, const uuid_le *uuid)
+{
+       struct mei_me_client *me_cl;
+       int ret;
+
+       if (!dev->hbm_f_vt_supported)
+               return -EOPNOTSUPP;
+
+       me_cl = mei_me_cl_by_uuid(dev, uuid);
+       if (!me_cl) {
+               dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
+                       uuid);
+               return -ENOTTY;
+       }
+       ret = me_cl->props.vt_supported ? 0 : -EOPNOTSUPP;
+       mei_me_cl_put(me_cl);
+
+       return ret;
+}
+
+/**
+ * mei_ioctl_connect_vtag - connect to fw client with vtag IOCTL function
+ *
+ * @file: private data of the file object
+ * @in_client_uuid: requested UUID for connection
+ * @client: IOCTL connect data, output parameters
+ * @vtag: vm tag
+ *
+ * Locking: called under "dev->device_lock" lock
+ *
+ * Return: 0 on success, <0 on failure.
+ */
+static int mei_ioctl_connect_vtag(struct file *file,
+                                 const uuid_le *in_client_uuid,
+                                 struct mei_client *client,
+                                 u8 vtag)
+{
+       struct mei_device *dev;
+       struct mei_cl *cl;
+       struct mei_cl *pos;
+       struct mei_cl_vtag *cl_vtag;
+
+       cl = file->private_data;
+       dev = cl->dev;
+
+       dev_dbg(dev->dev, "FW Client %pUl vtag %d\n", in_client_uuid, vtag);
+
+       switch (cl->state) {
+       case MEI_FILE_DISCONNECTED:
+               if (mei_cl_vtag_by_fp(cl, file) != vtag) {
+                       dev_err(dev->dev, "reconnect with different vtag\n");
+                       return -EINVAL;
+               }
+               break;
+       case MEI_FILE_INITIALIZING:
+               /* malicious connect from another thread may push vtag */
+               if (!IS_ERR(mei_cl_fp_by_vtag(cl, vtag))) {
+                       dev_err(dev->dev, "vtag already filled\n");
+                       return -EINVAL;
+               }
+
+               list_for_each_entry(pos, &dev->file_list, link) {
+                       if (pos == cl)
+                               continue;
+                       if (!pos->me_cl)
+                               continue;
+
+                       /* only search for same UUID */
+                       if (uuid_le_cmp(*mei_cl_uuid(pos), *in_client_uuid))
+                               continue;
+
+                       /* if tag already exist try another fp */
+                       if (!IS_ERR(mei_cl_fp_by_vtag(pos, vtag)))
+                               continue;
+
+                       /* replace cl with acquired one */
+                       dev_dbg(dev->dev, "replacing with existing cl\n");
+                       mei_cl_unlink(cl);
+                       kfree(cl);
+                       file->private_data = pos;
+                       cl = pos;
+                       break;
+               }
+
+               cl_vtag = mei_cl_vtag_alloc(file, vtag);
+               if (IS_ERR(cl_vtag))
+                       return -ENOMEM;
+
+               list_add_tail(&cl_vtag->list, &cl->vtag_map);
+               break;
+       default:
+               return -EBUSY;
+       }
+
+       while (cl->state != MEI_FILE_INITIALIZING &&
+              cl->state != MEI_FILE_DISCONNECTED &&
+              cl->state != MEI_FILE_CONNECTED) {
+               mutex_unlock(&dev->device_lock);
+               wait_event_timeout(cl->wait,
+                                  (cl->state == MEI_FILE_CONNECTED ||
+                                   cl->state == MEI_FILE_DISCONNECTED ||
+                                   cl->state == MEI_FILE_DISCONNECT_REQUIRED ||
+                                   cl->state == MEI_FILE_DISCONNECT_REPLY),
+                                  mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
+               mutex_lock(&dev->device_lock);
+       }
+
+       if (!mei_cl_is_connected(cl))
+               return mei_ioctl_connect_client(file, in_client_uuid, client);
+
+       client->max_msg_length = cl->me_cl->props.max_msg_length;
+       client->protocol_version = cl->me_cl->props.protocol_version;
+
+       return 0;
+}
+
 /**
  * mei_ioctl_client_notify_request -
  *     propagate event notification request to client
@@ -516,7 +642,11 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
 {
        struct mei_device *dev;
        struct mei_cl *cl = file->private_data;
-       struct mei_connect_client_data connect_data;
+       struct mei_connect_client_data conn;
+       struct mei_connect_client_data_vtag conn_vtag;
+       const uuid_le *cl_uuid;
+       struct mei_client *props;
+       u8 vtag;
        u32 notify_get, notify_req;
        int rets;
 
@@ -537,20 +667,68 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
        switch (cmd) {
        case IOCTL_MEI_CONNECT_CLIENT:
                dev_dbg(dev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n");
-               if (copy_from_user(&connect_data, (char __user *)data,
-                                  sizeof(connect_data))) {
+               if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) {
+                       dev_dbg(dev->dev, "failed to copy data from userland\n");
+                       rets = -EFAULT;
+                       goto out;
+               }
+               cl_uuid = &conn.in_client_uuid;
+               props = &conn.out_client_properties;
+               vtag = 0;
+
+               rets = mei_vt_support_check(dev, cl_uuid);
+               if (rets == -ENOTTY)
+                       goto out;
+               if (!rets)
+                       rets = mei_ioctl_connect_vtag(file, cl_uuid, props,
+                                                     vtag);
+               else
+                       rets = mei_ioctl_connect_client(file, cl_uuid, props);
+               if (rets)
+                       goto out;
+
+               /* if all is ok, copying the data back to user. */
+               if (copy_to_user((char __user *)data, &conn, sizeof(conn))) {
+                       dev_dbg(dev->dev, "failed to copy data to userland\n");
+                       rets = -EFAULT;
+                       goto out;
+               }
+
+               break;
+
+       case IOCTL_MEI_CONNECT_CLIENT_VTAG:
+               dev_dbg(dev->dev, "IOCTL_MEI_CONNECT_CLIENT_VTAG\n");
+               if (copy_from_user(&conn_vtag, (char __user *)data,
+                                  sizeof(conn_vtag))) {
                        dev_dbg(dev->dev, "failed to copy data from userland\n");
                        rets = -EFAULT;
                        goto out;
                }
 
-               rets = mei_ioctl_connect_client(file, &connect_data);
+               cl_uuid = &conn_vtag.connect.in_client_uuid;
+               props = &conn_vtag.out_client_properties;
+               vtag = conn_vtag.connect.vtag;
+
+               rets = mei_vt_support_check(dev, cl_uuid);
+               if (rets == -EOPNOTSUPP)
+                       dev_dbg(dev->dev, "FW Client %pUl does not support vtags\n",
+                               cl_uuid);
+               if (rets)
+                       goto out;
+
+               if (!vtag) {
+                       dev_dbg(dev->dev, "vtag can't be zero\n");
+                       rets = -EINVAL;
+                       goto out;
+               }
+
+               rets = mei_ioctl_connect_vtag(file, cl_uuid, props, vtag);
                if (rets)
                        goto out;
 
                /* if all is ok, copying the data back to user. */
-               if (copy_to_user((char __user *)data, &connect_data,
-                                sizeof(connect_data))) {
+               if (copy_to_user((char __user *)data, &conn_vtag,
+                                sizeof(conn_vtag))) {
                        dev_dbg(dev->dev, "failed to copy data to userland\n");
                        rets = -EFAULT;
                        goto out;