qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v2 3/3] linux-user: implement special usbfs ioctls.


From: Cortland Tölva
Subject: [Qemu-devel] [PATCH v2 3/3] linux-user: implement special usbfs ioctls.
Date: Tue, 25 Sep 2018 00:12:28 -0700

Userspace submits a USB Request Buffer to the kernel, optionally
discards it, and finally reaps the URB.  Thunk buffers from target
to host and back.

Tested by running an i386 scanner driver on ARMv7.  Neither the
discardurb ioctl nor the kernel's updating the argument to the
reap ioctl with a pointer to a reaped URB are exercised by this.

Signed-off-by: Cortland Tölva <address@hidden>
---
Changes from v1:
  improve pointer cast to int compatibility
  remove unimplemented types for usb streams
  struct definitions moved to this patch where possible

 linux-user/ioctls.h        |   8 +++
 linux-user/syscall.c       | 168 +++++++++++++++++++++++++++++++++++++++++++++
 linux-user/syscall_defs.h  |   4 ++
 linux-user/syscall_types.h |  20 ++++++
 4 files changed, 200 insertions(+)

diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
index 92f6177f1d..f04461fff7 100644
--- a/linux-user/ioctls.h
+++ b/linux-user/ioctls.h
@@ -143,6 +143,14 @@
   IOCTL(USBDEVFS_SETCONFIGURATION, IOC_W, MK_PTR(TYPE_INT))
   IOCTL(USBDEVFS_GETDRIVER, IOC_R,
         MK_PTR(MK_STRUCT(STRUCT_usbdevfs_getdriver)))
+  IOCTL_SPECIAL(USBDEVFS_SUBMITURB, IOC_W, do_ioctl_usbdevfs_submiturb,
+      MK_PTR(MK_STRUCT(STRUCT_usbdevfs_urb)))
+  IOCTL_SPECIAL(USBDEVFS_DISCARDURB, IOC_RW, do_ioctl_usbdevfs_discardurb,
+      MK_PTR(MK_STRUCT(STRUCT_usbdevfs_urb)))
+  IOCTL_SPECIAL(USBDEVFS_REAPURB, IOC_R, do_ioctl_usbdevfs_reapurb,
+      MK_PTR(MK_STRUCT(STRUCT_usbdevfs_urb)))
+  IOCTL_SPECIAL(USBDEVFS_REAPURBNDELAY, IOC_R, do_ioctl_usbdevfs_reapurb,
+      MK_PTR(MK_STRUCT(STRUCT_usbdevfs_urb)))
   IOCTL(USBDEVFS_DISCSIGNAL, IOC_W,
         MK_PTR(MK_STRUCT(STRUCT_usbdevfs_disconnectsignal)))
   IOCTL(USBDEVFS_CLAIMINTERFACE, IOC_W, MK_PTR(TYPE_INT))
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 39f21b78c8..bb0bf7e03d 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -5491,6 +5491,174 @@ static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, 
uint8_t *buf_temp,
     return ret;
 }
 
+#if defined(CONFIG_USBFS)
+#if HOST_LONG_BITS > 64
+#error USBDEVFS thunks do not support >64 bit hosts yet.
+#endif
+static GHashTable *usbdevfs_urb_hashtable(void)
+{
+    static GHashTable *urb_hashtable;
+
+    if (!urb_hashtable) {
+        urb_hashtable = g_hash_table_new(g_int64_hash, g_int64_equal);
+    }
+    return urb_hashtable;
+}
+
+static abi_long
+do_ioctl_usbdevfs_reapurb(const IOCTLEntry *ie, uint8_t *buf_temp,
+                          int fd, int cmd, abi_long arg)
+{
+    const argtype *arg_type = ie->arg_type;
+    GHashTable * const urb_hash = usbdevfs_urb_hashtable();
+    const argtype ptrvoid_arg_type[] = { TYPE_PTRVOID, 0, 0 };
+    int target_size;
+    int64_t reaped_userurb;
+    int64_t target_urbptr;
+    uintptr_t target_urbptr_ptr;
+    char *tagged_urb;
+    void *argptr;
+    abi_long ret;
+
+    target_size = thunk_type_size(++arg_type, THUNK_TARGET);
+
+    ret = get_errno(safe_ioctl(fd, ie->host_cmd, buf_temp));
+    if (is_error(ret)) {
+        return ret;
+    }
+
+    memcpy(&reaped_userurb, buf_temp, sizeof(int64_t));
+    tagged_urb = ((char *)(uintptr_t)reaped_userurb) - sizeof(int64_t);
+    memcpy(&target_urbptr, tagged_urb, sizeof(int64_t));
+    if (!target_urbptr) {
+        return -TARGET_EFAULT;
+    }
+    g_hash_table_remove(urb_hash, tagged_urb);
+
+    argptr = lock_user(VERIFY_WRITE, (abi_long) target_urbptr, target_size, 0);
+    if (!argptr) {
+        g_free(tagged_urb);
+        return -TARGET_EFAULT;
+    }
+    thunk_convert(argptr, (char *)(uintptr_t)reaped_userurb, arg_type,
+                  THUNK_TARGET);
+    unlock_user(argptr, target_urbptr, target_size);
+
+    target_size = thunk_type_size(ptrvoid_arg_type, THUNK_TARGET);
+    argptr = lock_user(VERIFY_WRITE, (abi_long)arg, target_size, 0);
+    if (!argptr) {
+        g_free(tagged_urb);
+        return -TARGET_EFAULT;
+    }
+    target_urbptr_ptr = (uintptr_t)target_urbptr;
+    thunk_convert(argptr, &target_urbptr_ptr, ptrvoid_arg_type, THUNK_TARGET);
+    unlock_user(argptr, (abi_long) arg, target_size);
+    g_free(tagged_urb);
+    return ret;
+}
+
+static abi_long
+do_ioctl_usbdevfs_discardurb(const IOCTLEntry *ie,
+                             uint8_t *buf_temp __attribute__((unused)),
+                             int fd, int cmd, abi_long arg)
+{
+    GHashTable * const urb_hash = usbdevfs_urb_hashtable();
+    abi_long host_urb;
+    int64_t tag_urb_key;
+    char *tagged_urb;
+
+    /* map target pointer back to host tagged URB. */
+    tag_urb_key = (int64_t)arg;
+    tagged_urb = g_hash_table_lookup(urb_hash, &tag_urb_key);
+    if (!tagged_urb) {
+        return -TARGET_EFAULT;
+    }
+    /* offset from tag to urb */
+    host_urb = (abi_long)(uintptr_t)(tagged_urb + sizeof(int64_t));
+    return get_errno(safe_ioctl(fd, ie->host_cmd, host_urb));
+}
+
+static int convert_iso_packets(uint8_t *dst, int totlen, abi_long src)
+{
+    void *srcptr;
+    int host_size, target_size;
+    int i, iso_packets;
+    const argtype arg_type[] = { MK_STRUCT(STRUCT_usbdevfs_iso_packet_desc) };
+
+    if (((struct usbdevfs_urb *)dst)->type == USBDEVFS_URB_TYPE_ISO) {
+        iso_packets = ((struct usbdevfs_urb *)dst)->number_of_packets;
+    } else {
+        iso_packets = 0;
+    }
+
+    host_size = thunk_type_size(arg_type, THUNK_HOST);
+    target_size = thunk_type_size(arg_type, THUNK_TARGET);
+
+    for (i = 0; i < iso_packets; ++i) {
+        if ((totlen + host_size) >= MAX_STRUCT_SIZE) {
+            break;
+        }
+        srcptr = lock_user(VERIFY_READ, src, target_size, 1);
+        thunk_convert(dst + totlen, srcptr, arg_type, THUNK_HOST);
+        unlock_user(srcptr, src, 0);
+        src += target_size;
+        totlen += host_size;
+    }
+    return totlen;
+}
+
+static abi_long
+do_ioctl_usbdevfs_submiturb(const IOCTLEntry *ie, uint8_t *buf_temp,
+                            int fd, int cmd, abi_long arg)
+{
+    const argtype *arg_type = ie->arg_type;
+    int target_size;
+    int host_size;
+    abi_long ret;
+    char *tagged_urb = NULL;
+    void *argptr;
+    int64_t arg_tag;
+    GHashTable * const urb_hash = usbdevfs_urb_hashtable();
+
+    /*
+     * each submitted URB needs to map to a unique ID for the
+     * kernel, and that unique ID needs to be a pointer to
+     * host memory.  hence, we need to malloc for each URB.
+     * isochronous transfers have a variable length struct.
+     */
+    arg_type++;
+    host_size = thunk_type_size(arg_type, THUNK_HOST);
+    target_size = thunk_type_size(arg_type, THUNK_TARGET);
+
+    argptr = lock_user(VERIFY_READ, arg, target_size, 1);
+    if (!argptr) {
+        return -TARGET_EFAULT;
+    }
+    thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
+    unlock_user(argptr, arg, 0);
+
+    host_size = convert_iso_packets(buf_temp, host_size, arg + target_size);
+
+    /* allocate extra space for a tag. */
+    tagged_urb = g_try_malloc0(host_size + sizeof(int64_t));
+    if (!tagged_urb) {
+        return -TARGET_ENOMEM;
+    }
+    memcpy(&tagged_urb[sizeof(int64_t)], buf_temp, host_size);
+
+    ret = get_errno(safe_ioctl(fd, ie->host_cmd, 
&tagged_urb[sizeof(int64_t)]));
+    if (is_error(ret)) {
+        g_free(tagged_urb);
+    } else {
+        arg_tag = (int64_t) arg;
+        memcpy(tagged_urb, &arg_tag, sizeof(int64_t));
+        g_hash_table_insert(urb_hash, tagged_urb, tagged_urb);
+    }
+
+    return ret;
+}
+#endif /* CONFIG_USBFS */
+
 static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
                             int cmd, abi_long arg)
 {
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
index 357ee6a8c2..eeb7204b9e 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -866,6 +866,10 @@ struct target_pollfd {
 #define TARGET_USBDEVFS_SETINTERFACE TARGET_IOR('U', 4, struct 
usbdevfs_setinterface)
 #define TARGET_USBDEVFS_SETCONFIGURATION TARGET_IOR('U',  5, int)
 #define TARGET_USBDEVFS_GETDRIVER TARGET_IOW('U', 8, struct usbdevfs_getdriver)
+#define TARGET_USBDEVFS_SUBMITURB TARGET_IOR('U', 10, struct usbdevfs_urb)
+#define TARGET_USBDEVFS_DISCARDURB TARGET_IO('U', 11)
+#define TARGET_USBDEVFS_REAPURB TARGET_IOW('U', 12, int)
+#define TARGET_USBDEVFS_REAPURBNDELAY TARGET_IOW('U', 13, int)
 #define TARGET_USBDEVFS_DISCSIGNAL TARGET_IOR('U', 14, struct 
usbdevfs_disconnectsignal)
 #define TARGET_USBDEVFS_CLAIMINTERFACE TARGET_IOR('U', 15, int)
 #define TARGET_USBDEVFS_RELEASEINTERFACE TARGET_IOR('U', 16, int)
diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h
index 6f64a8bdf7..b98a23b0f1 100644
--- a/linux-user/syscall_types.h
+++ b/linux-user/syscall_types.h
@@ -300,6 +300,26 @@ STRUCT(usbdevfs_connectinfo,
         TYPE_INT, /* devnum */
         TYPE_CHAR) /* slow */
 
+STRUCT(usbdevfs_iso_packet_desc,
+        TYPE_INT, /* length */
+        TYPE_INT, /* actual_length */
+        TYPE_INT) /* status */
+
+STRUCT(usbdevfs_urb,
+        TYPE_CHAR, /* type */
+        TYPE_CHAR, /* endpoint */
+        TYPE_INT, /* status */
+        TYPE_INT, /* flags */
+        TYPE_PTRVOID, /* buffer */
+        TYPE_INT, /* buffer_length */
+        TYPE_INT, /* actual_length */
+        TYPE_INT, /* start_frame */
+        TYPE_INT, /* union number_of_packets stream_id */
+        TYPE_INT, /* error_count */
+        TYPE_INT, /* signr */
+        TYPE_PTRVOID, /* usercontext */
+        MK_ARRAY(MK_STRUCT(STRUCT_usbdevfs_iso_packet_desc), 0)) /* desc */
+
 STRUCT(usbdevfs_ioctl,
         TYPE_INT, /* ifno */
         TYPE_INT, /* ioctl_code */
-- 
2.11.0



reply via email to

[Prev in Thread] Current Thread [Next in Thread]