diff mbox series

[3/3] linux-user: implement special usbfs ioctls.

Message ID 20180919080421.24011-4-cst@tolva.net
State New
Headers show
Series Linux usermode emulation user mode USB driver support. | expand

Commit Message

Cortland Setlow Tölva Sept. 19, 2018, 8:04 a.m. UTC
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 <cst@tolva.net>
---
 linux-user/ioctls.h  |   9 +++
 linux-user/syscall.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 177 insertions(+)

Comments

Laurent Vivier Sept. 24, 2018, 4:57 p.m. UTC | #1
Le 19/09/2018 à 10:04, Cortland Tölva a écrit :
> 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 <cst@tolva.net>
> ---
>  linux-user/ioctls.h  |   9 +++
>  linux-user/syscall.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 177 insertions(+)
> 
It looks good (for what I understand of usbdevfs...), please move the
usbdevfs_urb parts from PATCH 2 to this one.

Reviewed-by: Laurent Vivier <laurent@vivier.eu>

Thanks,
LAurent
diff mbox series

Patch

diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h
index 92f6177f1d..0118fa7e64 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..b66688277b 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) (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)
 {