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

[tyndur-devel] [PATCH v2 18/24] cdi/usb: Implementierung der CDI-Bibliothek



+ Implementierung der CDI-Bibliothek für CDI.usb für týndur

Signed-off-by: Max Reitz <max@xxxxxxxxxx>
---
 src/modules/cdi/include/cdi-osdep.h |   3 +
 src/modules/cdi/lib/cdi.c           |  81 +++++++
 src/modules/cdi/lib/usb.c           | 403 +++++++++++++++++++++++++++++++++++
 src/modules/cdi/lib/usb_dd.c        | 173 +++++++++++++++
 src/modules/cdi/lib/usb_hcd.c       | 413 ++++++++++++++++++++++++++++++++++++
 src/modules/include/usb-ipc.h       | 103 +++++++++
 6 files changed, 1176 insertions(+)
 create mode 100644 src/modules/cdi/lib/usb.c
 create mode 100644 src/modules/cdi/lib/usb_dd.c
 create mode 100644 src/modules/cdi/lib/usb_hcd.c
 create mode 100644 src/modules/include/usb-ipc.h

diff --git a/src/modules/cdi/include/cdi-osdep.h b/src/modules/cdi/include/cdi-osdep.h
index 3ec20a1..42fa39d 100644
--- a/src/modules/cdi/include/cdi-osdep.h
+++ b/src/modules/cdi/include/cdi-osdep.h
@@ -13,6 +13,7 @@
 
 #include <stdio.h>
 #include <lostio.h>
+#include <sys/types.h>
 
 #define CDI_STANDALONE
 #define TYNDUR
@@ -75,6 +76,8 @@ typedef struct
  * \endjapanese
  */
 typedef struct {
+    pid_t pid;
+    int id;
 } cdi_usb_device_osdep;
 
 /**
diff --git a/src/modules/cdi/lib/cdi.c b/src/modules/cdi/lib/cdi.c
index 11b3009..fa8a217 100644
--- a/src/modules/cdi/lib/cdi.c
+++ b/src/modules/cdi/lib/cdi.c
@@ -23,11 +23,20 @@
 #include "cdi/pci.h"
 #include "cdi/scsi.h"
 #include "cdi/storage.h"
+#include "cdi/usb.h"
+#include "cdi/usb_hcd.h"
 
 extern void cdi_storage_driver_register(struct cdi_storage_driver* driver);
 extern void cdi_audio_driver_register(struct cdi_audio_driver* driver);
+extern void cdi_usb_driver_register(struct cdi_usb_driver* driver);
 extern void cdi_tyndur_net_device_init(struct cdi_device* device);
 
+extern void cdi_osdep_handle_usb_device(struct cdi_usb_bus_device_pattern* p);
+
+extern void cdi_osdep_provide_hc(struct cdi_usb_hc* hc);
+
+extern void cdi_osdep_set_up_usb_dd_ipc(struct cdi_driver* drv);
+
 void cdi_osdep_new_device(struct cdi_driver* drv, struct cdi_device* dev);
 
 static list_t* drivers = NULL;
@@ -219,6 +228,10 @@ static void cdi_tyndur_run_drivers(void)
             }
         }
 
+        if (driver->bus == CDI_USB) {
+            cdi_osdep_set_up_usb_dd_ipc(driver);
+        }
+
         /* Netzwerk ist schon initialisiert */
         if (driver->type != CDI_NETWORK) {
             init_service_register((char*) driver->name);
@@ -270,6 +283,10 @@ void cdi_driver_register(struct cdi_driver* driver)
             cdi_audio_driver_register((struct cdi_audio_driver*) driver);
             break;
 
+        case CDI_USB:
+            cdi_usb_driver_register((struct cdi_usb_driver*) driver);
+            break;
+
         default:
             break;
     }
@@ -281,6 +298,66 @@ list_t* cdi_tyndur_get_drivers(void)
     return drivers;
 }
 
+void cdi_handle_bus_device(struct cdi_driver* drv,
+                           struct cdi_bus_device_pattern* pattern)
+{
+    (void)drv;
+
+    switch ((int)pattern->bus_type) {
+        case CDI_USB:
+            cdi_osdep_handle_usb_device(
+                CDI_UPCAST(pattern, struct cdi_usb_bus_device_pattern,
+                           pattern));
+            break;
+    }
+}
+
+extern void cdi_osdep_provide_usb_device_to(struct cdi_usb_device* dev,
+                                            pid_t pid);
+
+int cdi_provide_device(struct cdi_bus_data* device)
+{
+    if (device->bus_type != CDI_USB) {
+        return -1;
+    }
+
+    struct cdi_usb_device* usb_dev = CDI_UPCAST(device, struct cdi_usb_device,
+                                                bus_data);
+    pid_t pid;
+    char name[32];
+
+    sprintf(name, "usb-%04x-%04x", usb_dev->vendor_id, usb_dev->product_id);
+    pid = init_service_get(name);
+    if (pid > 0) {
+        cdi_osdep_provide_usb_device_to(usb_dev, pid);
+        return 0;
+    }
+
+    sprintf(name, "usb-%02x-%02x-%02x", usb_dev->class_id, usb_dev->subclass_id,
+            usb_dev->protocol_id);
+    pid = init_service_get(name);
+    if (pid > 0) {
+        cdi_osdep_provide_usb_device_to(usb_dev, pid);
+        return 0;
+    }
+
+    sprintf(name, "usb-%02x-%02x-?", usb_dev->class_id, usb_dev->subclass_id);
+    pid = init_service_get(name);
+    if (pid > 0) {
+        cdi_osdep_provide_usb_device_to(usb_dev, pid);
+        return 0;
+    }
+
+    sprintf(name, "usb-%02x-?-?", usb_dev->class_id);
+    pid = init_service_get(name);
+    if (pid > 0) {
+        cdi_osdep_provide_usb_device_to(usb_dev, pid);
+        return 0;
+    }
+
+    return -1;
+}
+
 void cdi_osdep_new_device(struct cdi_driver* drv, struct cdi_device* dev)
 {
     if (!dev) {
@@ -294,6 +371,10 @@ void cdi_osdep_new_device(struct cdi_driver* drv, struct cdi_device* dev)
         case CDI_SCSI:
             cdi_scsi_device_init(CDI_UPCAST(dev, struct cdi_scsi_device, dev));
             break;
+
+        case CDI_USB_HCD:
+            cdi_osdep_provide_hc(CDI_UPCAST(dev, struct cdi_usb_hc, dev));
+            break;
     }
 }
 
diff --git a/src/modules/cdi/lib/usb.c b/src/modules/cdi/lib/usb.c
new file mode 100644
index 0000000..69da43f
--- /dev/null
+++ b/src/modules/cdi/lib/usb.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2015 Max Reitz
+ *
+ * This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+ */
+
+#include <assert.h>
+#include <collections.h>
+#include <rpc.h>
+#include <services.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <syscall.h>
+#include <unistd.h>
+#include <usb-ipc.h>
+
+#include "cdi.h"
+#include "cdi/lists.h"
+#include "cdi/misc.h"
+#include "cdi/usb.h"
+#include "cdi/usb_hcd.h"
+
+
+static struct cdi_usb_driver* usb_drv;
+static struct cdi_usb_device** dev_list;
+static size_t dev_list_size;
+
+extern void cdi_osdep_new_device(struct cdi_driver* drv,
+                                 struct cdi_device* dev);
+
+#define RPC(name) \
+    static void name(pid_t src, uint32_t corr_id, size_t length, void* data)
+
+#define rpc_int_resp(val) rpc_send_int_response(src, corr_id, val)
+
+
+struct transmission {
+    cdi_usb_hc_transaction_t ta;
+
+    void* buffer;
+    size_t size;
+
+    cdi_usb_transmission_result_t* result_ptr;
+
+    struct {
+        cdi_usb_transmission_result_t result;
+        char data[];
+    } *shm;
+    uint32_t shmid;
+};
+
+struct ipc_usb_hc {
+    struct cdi_usb_hc cdi;
+    pid_t pid;
+    int id;
+
+    list_t* open_transmissions;
+};
+
+
+RPC(hc_found);
+RPC(get_endpoint_descriptor);
+RPC(control_transfer);
+RPC(bulk_transfer);
+
+
+void cdi_usb_driver_register(struct cdi_usb_driver* drv)
+{
+    assert(!usb_drv);
+    usb_drv = drv;
+
+    // Wait for all USB device drivers
+    servmgr_need("usb-storage");
+
+    register_message_handler(USB_IPC_HC_FOUND, hc_found);
+    register_message_handler(USB_IPC_GET_ENDPOINT_DESCRIPTOR,
+                             get_endpoint_descriptor);
+    register_message_handler(USB_IPC_CONTROL_TRANSFER, control_transfer);
+    register_message_handler(USB_IPC_BULK_TRANSFER, bulk_transfer);
+}
+
+
+static void rh_port_down(struct cdi_usb_hub* hub, int index)
+{
+    struct cdi_usb_hc* hc = CDI_UPCAST(hub, struct cdi_usb_hc, rh);
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_port_param par = {
+        .hc = ipc_hc->id,
+        .index = index
+    };
+
+    rpc_get_int(ipc_hc->pid, USB_IPC_RH_PORT_DISABLE, sizeof(par),
+                (char*) &par);
+}
+
+
+static void rh_port_up(struct cdi_usb_hub* hub, int index)
+{
+    struct cdi_usb_hc* hc = CDI_UPCAST(hub, struct cdi_usb_hc, rh);
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_port_param par = {
+        .hc = ipc_hc->id,
+        .index = index
+    };
+
+    rpc_get_int(ipc_hc->pid, USB_IPC_RH_PORT_RESET_ENABLE, sizeof(par),
+                (char*) &par);
+}
+
+
+static cdi_usb_port_status_t rh_port_status(struct cdi_usb_hub* hub, int index)
+{
+    struct cdi_usb_hc* hc = CDI_UPCAST(hub, struct cdi_usb_hc, rh);
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_port_param par = {
+        .hc = ipc_hc->id,
+        .index = index
+    };
+
+    return rpc_get_dword(ipc_hc->pid, USB_IPC_RH_PORT_STATUS, sizeof(par),
+                         (char*) &par);
+}
+
+
+static cdi_usb_hc_transaction_t
+    hc_create_transaction(struct cdi_usb_hc* hc,
+                          const struct cdi_usb_hc_ep_info* target)
+{
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_create_transaction_param par = {
+        .hc = ipc_hc->id,
+        .target = *target
+    };
+
+    response_t* resp = rpc_get_response(ipc_hc->pid,
+                                        USB_IPC_HC_CREATE_TRANSACTION,
+                                        sizeof(par), (char*) &par);
+    if (resp->data_length != sizeof(cdi_usb_hc_transaction_t)) {
+        free(resp->data);
+        free(resp);
+        return NULL;
+    }
+
+    cdi_usb_hc_transaction_t ta = *(cdi_usb_hc_transaction_t*) resp->data;
+    free(resp->data);
+    free(resp);
+
+    return ta;
+}
+
+
+static void hc_enqueue(struct cdi_usb_hc* hc,
+                       const struct cdi_usb_hc_transmission* trans)
+{
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct transmission* int_trans = calloc(1, sizeof(*trans));
+
+    int_trans->ta = trans->ta;
+    int_trans->result_ptr = trans->result;
+
+    int_trans->shmid = create_shared_memory(trans->size
+                                            + sizeof(*trans->result));
+    int_trans->shm = open_shared_memory(int_trans->shmid);
+
+    if (trans->token == CDI_USB_IN) {
+        int_trans->buffer = trans->buffer;
+        int_trans->size   = trans->size;
+    } else {
+        memcpy(int_trans->shm->data, trans->buffer, trans->size);
+    }
+
+    list_push(ipc_hc->open_transmissions, int_trans);
+
+    struct usb_ipc_hc_enqueue_param par = {
+        .hc = ipc_hc->id,
+        .trans = *trans,
+        .shm = int_trans->shmid
+    };
+
+    rpc_get_int(ipc_hc->pid, USB_IPC_HC_ENQUEUE, sizeof(par), (char*) &par);
+}
+
+
+static void hc_start_transaction(struct cdi_usb_hc* hc,
+                                 cdi_usb_hc_transaction_t ta)
+{
+    struct ipc_usb_hc *ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_start_transaction_param par = {
+        .hc = ipc_hc->id,
+        .ta = ta
+    };
+
+    rpc_get_int(ipc_hc->pid, USB_IPC_HC_START_TRANSACTION,
+                sizeof(par), (char*) &par);
+}
+
+
+static void hc_wait_transaction(struct cdi_usb_hc* hc,
+                                cdi_usb_hc_transaction_t ta)
+{
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_wait_transaction_param par = {
+        .hc = ipc_hc->id,
+        .ta = ta
+    };
+
+    rpc_get_int(ipc_hc->pid, USB_IPC_HC_WAIT_TRANSACTION, sizeof(par),
+                (char*) &par);
+
+    int i = 0;
+    struct transmission* int_trans;
+    while ((int_trans = list_get_element_at(ipc_hc->open_transmissions, i++))) {
+        if (int_trans->ta == ta) {
+            list_remove(ipc_hc->open_transmissions, --i);
+
+            if (int_trans->result_ptr) {
+                *int_trans->result_ptr = int_trans->shm->result;
+            }
+
+            if (int_trans->buffer) {
+                memcpy(int_trans->buffer, int_trans->shm->data,
+                       int_trans->size);
+            }
+
+            close_shared_memory(int_trans->shmid);
+            free(int_trans);
+        }
+    }
+}
+
+
+static void hc_destroy_transaction(struct cdi_usb_hc* hc,
+                                   cdi_usb_hc_transaction_t ta)
+{
+    struct ipc_usb_hc* ipc_hc = CDI_UPCAST(hc, struct ipc_usb_hc, cdi);
+    struct usb_ipc_hc_wait_transaction_param par = {
+        .hc = ipc_hc->id,
+        .ta = ta
+    };
+
+    rpc_get_int(ipc_hc->pid, USB_IPC_HC_DESTROY_TRANSACTION, sizeof(par),
+                (char*) &par);
+
+    int i = 0;
+    struct transmission* int_trans;
+    while ((int_trans = list_get_element_at(ipc_hc->open_transmissions, i++))) {
+        if (int_trans->ta == ta) {
+            list_remove(ipc_hc->open_transmissions, --i);
+            close_shared_memory(int_trans->shmid);
+            free(int_trans);
+        }
+    }
+}
+
+
+static struct cdi_usb_hcd wrapper_hcd = {
+    .drv = {
+        .name   = "ipc-hci",
+        .type   = CDI_USB_HCD,
+    },
+
+    .rh_drv = {
+        .port_disable       = rh_port_down,
+        .port_reset_enable  = rh_port_up,
+        .port_status        = rh_port_status,
+    },
+
+    .create_transaction     = hc_create_transaction,
+    .enqueue                = hc_enqueue,
+    .start_transaction      = hc_start_transaction,
+    .wait_transaction       = hc_wait_transaction,
+    .destroy_transaction    = hc_destroy_transaction,
+};
+
+
+RPC(hc_found)
+{
+    struct usb_ipc_hc_found_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct cdi_usb_hcd* hcd = malloc(sizeof(*hcd));
+    *hcd = wrapper_hcd;
+    hcd->drv.name = strdup(par->drv_name);
+
+    struct ipc_usb_hc* hc = calloc(1, sizeof(*hc));
+    hc->pid = src;
+    hc->id = par->id;
+    hc->open_transmissions = list_create();
+    hc->cdi.dev.driver = &hcd->drv;
+    hc->cdi.dev.name = strdup(par->hc_name);
+    hc->cdi.rh = par->rh;
+
+    struct cdi_usb_hc_bus* hcb = calloc(1, sizeof(*hcb));
+    hcb->bus_data.bus_type = CDI_USB_HCD;
+    hcb->hc = &hc->cdi;
+
+    cdi_osdep_new_device(&usb_drv->drv,
+                         usb_drv->drv.init_device(&hcb->bus_data));
+
+    rpc_int_resp(1);
+}
+
+
+RPC(get_endpoint_descriptor)
+{
+    struct usb_ipc_get_endpoint_descriptor_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->dev < 0 || par->dev >= (int)dev_list_size || !dev_list[par->dev]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct cdi_usb_device* dev = dev_list[par->dev];
+    struct cdi_usb_endpoint_descriptor desc;
+
+    usb_drv->get_endpoint_descriptor(dev, par->ep, &desc);
+    rpc_send_response(src, corr_id, sizeof(desc), (char*) &desc);
+}
+
+
+RPC(control_transfer)
+{
+    struct usb_ipc_control_transfer_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_send_dword_response(src, corr_id, CDI_USB_DRIVER_ERROR);
+        return;
+    }
+
+    if (par->dev < 0 || par->dev >= (int)dev_list_size || !dev_list[par->dev]) {
+        rpc_send_dword_response(src, corr_id, CDI_USB_DRIVER_ERROR);
+        return;
+    }
+
+    struct cdi_usb_device* dev = dev_list[par->dev];
+    void* shm = open_shared_memory(par->shm);
+
+    cdi_usb_transmission_result_t res;
+    res = usb_drv->control_transfer(dev, par->ep, &par->setup, shm);
+
+    close_shared_memory(par->shm);
+
+    rpc_send_dword_response(src, corr_id, res);
+}
+
+
+RPC(bulk_transfer)
+{
+    struct usb_ipc_bulk_transfer_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_send_dword_response(src, corr_id, CDI_USB_DRIVER_ERROR);
+        return;
+    }
+
+    if (par->dev < 0 || par->dev >= (int)dev_list_size || !dev_list[par->dev]) {
+        rpc_send_dword_response(src, corr_id, CDI_USB_DRIVER_ERROR);
+        return;
+    }
+
+    struct cdi_usb_device* dev = dev_list[par->dev];
+    void* shm = open_shared_memory(par->shm);
+
+    cdi_usb_transmission_result_t res;
+    res = usb_drv->bulk_transfer(dev, par->ep, shm, par->size);
+
+    close_shared_memory(par->shm);
+
+    rpc_send_dword_response(src, corr_id, res);
+}
+
+
+void cdi_osdep_provide_usb_device_to(struct cdi_usb_device* usb_dev, pid_t pid);
+
+void cdi_osdep_provide_usb_device_to(struct cdi_usb_device* usb_dev, pid_t pid)
+{
+    int idx;
+    for (idx = 0; idx < (int)dev_list_size && !dev_list[idx]; idx++);
+
+    if (idx == (int)dev_list_size) {
+        dev_list_size = (dev_list_size + 4) * 3 / 2;
+        dev_list = realloc(dev_list, dev_list_size * sizeof(*dev_list));
+        memset(dev_list + idx, 0, (dev_list_size - idx) * sizeof(*dev_list));
+    }
+
+    dev_list[idx] = usb_dev;
+    dev_list[idx]->meta.pid = getpid();
+    dev_list[idx]->meta.id = idx;
+
+    rpc_get_int(pid, USB_IPC_DEVICE_FOUND,
+                sizeof(*dev_list[idx]), (char*) dev_list[idx]);
+}
diff --git a/src/modules/cdi/lib/usb_dd.c b/src/modules/cdi/lib/usb_dd.c
new file mode 100644
index 0000000..e1a379d
--- /dev/null
+++ b/src/modules/cdi/lib/usb_dd.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2015 Max Reitz
+ *
+ * This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+ */
+
+#include <assert.h>
+#include <init.h>
+#include <rpc.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+#include <unistd.h>
+#include <usb-ipc.h>
+
+#include "cdi.h"
+#include "cdi/misc.h"
+#include "cdi/usb.h"
+
+
+extern void cdi_osdep_new_device(struct cdi_driver* drv,
+                                 struct cdi_device* dev);
+
+static struct cdi_driver* usb_dd;
+
+#define RPC(name) \
+    static void name(pid_t src, uint32_t corr_id, size_t length, void* data)
+
+#define rpc_int_resp(val) rpc_send_int_response(src, corr_id, val)
+
+
+RPC(device_found)
+{
+    struct cdi_usb_device* dev = data;
+    if (length != sizeof(*dev)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    cdi_osdep_new_device(usb_dd, usb_dd->init_device(&dev->bus_data));
+
+    rpc_int_resp(0);
+}
+
+
+void cdi_osdep_set_up_usb_dd_ipc(struct cdi_driver* drv);
+
+void cdi_osdep_set_up_usb_dd_ipc(struct cdi_driver* drv)
+{
+    assert(!usb_dd);
+    usb_dd = drv;
+
+    register_message_handler(USB_IPC_DEVICE_FOUND, device_found);
+}
+
+
+void cdi_osdep_handle_usb_device(struct cdi_usb_bus_device_pattern* p);
+
+void cdi_osdep_handle_usb_device(struct cdi_usb_bus_device_pattern* p)
+{
+    char alias[32];
+
+    if (p->vendor_id < 0 && p->product_id < 0) {
+        assert(p->class_id >= 0);
+        if (p->protocol_id < 0) {
+            assert(p->subclass_id < 0);
+            snprintf(alias, 32, "usb-%02x-?-?", p->class_id);
+        } else if (p->subclass_id < 0) {
+            snprintf(alias, 32, "usb-%02x-%02x-?", p->class_id, p->subclass_id);
+        } else {
+            snprintf(alias, 32, "usb-%02x-%02x-%02x", p->class_id,
+                     p->subclass_id, p->protocol_id);
+        }
+    } else if (p->class_id < 0 && p->subclass_id < 0 && p->protocol_id < 0) {
+        assert(p->vendor_id >= 0 && p->product_id >= 0);
+        snprintf(alias, 32, "usb-%04x-%04x", p->vendor_id, p->product_id);
+    } else {
+        assert(0);
+    }
+
+    init_service_register(alias);
+}
+
+
+void cdi_usb_get_endpoint_descriptor(struct cdi_usb_device* dev, int ep,
+                                     struct cdi_usb_endpoint_descriptor* desc)
+{
+    struct usb_ipc_get_endpoint_descriptor_param par = {
+        .dev   = dev->meta.id,
+        .ep    = ep
+    };
+
+    response_t* resp = rpc_get_response(dev->meta.pid,
+                                        USB_IPC_GET_ENDPOINT_DESCRIPTOR,
+                                        sizeof(par), (char*) &par);
+    assert(resp->data_length == sizeof(*desc));
+
+    memcpy(desc, resp->data, sizeof(*desc));
+    free(resp->data);
+    free(resp);
+}
+
+
+cdi_usb_transmission_result_t
+    cdi_usb_control_transfer(struct cdi_usb_device* dev, int ep,
+                             const struct cdi_usb_setup_packet* setup,
+                             void* data)
+{
+    uint32_t shmid = 0;
+    void* shm = NULL;
+
+    if (setup->w_length) {
+        shmid = create_shared_memory(setup->w_length);
+        shm = open_shared_memory(shmid);
+        if (setup->bm_request_type & CDI_USB_CREQ_OUT) {
+            memcpy(shm, data, setup->w_length);
+        }
+    }
+
+    struct usb_ipc_control_transfer_param par = {
+        .dev   = dev->meta.id,
+        .ep    = ep,
+        .setup = *setup,
+        .shm   = shmid
+    };
+
+    cdi_usb_transmission_result_t res;
+    res = rpc_get_dword(dev->meta.pid, USB_IPC_CONTROL_TRANSFER,
+                        sizeof(par), (char*) &par);
+
+    if (setup->w_length) {
+        if (setup->bm_request_type & CDI_USB_CREQ_IN) {
+            memcpy(data, shm, setup->w_length);
+        }
+        close_shared_memory(shmid);
+    }
+
+    return res;
+}
+
+
+cdi_usb_transmission_result_t cdi_usb_bulk_transfer(struct cdi_usb_device* dev,
+                                                    int ep, void* data,
+                                                    size_t size)
+{
+    uint32_t shmid = create_shared_memory(size);
+    void* shm = open_shared_memory(shmid);
+
+    // FIXME (We don't know which direction the transfer is going)
+    memcpy(shm, data, size);
+
+    struct usb_ipc_bulk_transfer_param par = {
+        .dev   = dev->meta.id,
+        .ep    = ep,
+        .size  = size,
+        .shm   = shmid
+    };
+
+    cdi_usb_transmission_result_t res;
+    res = rpc_get_dword(dev->meta.pid, USB_IPC_BULK_TRANSFER,
+                        sizeof(par), (char*) &par);
+
+    // FIXME (Same as above)
+    memcpy(data, shm, size);
+
+    close_shared_memory(shmid);
+
+    return res;
+}
diff --git a/src/modules/cdi/lib/usb_hcd.c b/src/modules/cdi/lib/usb_hcd.c
new file mode 100644
index 0000000..b06722c
--- /dev/null
+++ b/src/modules/cdi/lib/usb_hcd.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2015 Max Reitz
+ *
+ * This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+ */
+
+#include <collections.h>
+#include <init.h>
+#include <rpc.h>
+#include <services.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+#include <unistd.h>
+#include <usb-ipc.h>
+
+#include "cdi/misc.h"
+#include "cdi/usb_hcd.h"
+
+
+extern void cdi_osdep_device_found(void);
+
+
+struct transmission {
+    uint32_t shmid;
+    void* mapping;
+};
+
+struct transaction {
+    cdi_usb_hc_transaction_t ta;
+    list_t* transmissions;
+    bool started;
+};
+
+struct usb_hc {
+    struct cdi_usb_hc* cdi;
+
+    struct transaction** transactions;
+    size_t transactions_size;
+    size_t transactions_first_free;
+};
+
+static struct usb_hc** hc_list;
+static size_t hc_list_size, hc_list_elements;
+
+#define RPC(name) \
+    static void name(pid_t src, uint32_t corr_id, size_t length, void* data)
+
+#define rpc_int_resp(val) rpc_send_int_response(src, corr_id, val)
+
+
+RPC(hc_create_transaction);
+RPC(hc_enqueue);
+RPC(hc_start_transaction);
+RPC(hc_wait_transaction);
+RPC(hc_destroy_transaction);
+
+RPC(rh_port_disable);
+RPC(rh_port_reset_enable);
+RPC(rh_port_status);
+
+
+static void set_up_hc_ipc(void)
+{
+    static bool ipc_set_up;
+
+    if (ipc_set_up) {
+        return;
+    }
+    ipc_set_up = true;
+
+    register_message_handler(USB_IPC_HC_CREATE_TRANSACTION,
+                             hc_create_transaction);
+    register_message_handler(USB_IPC_HC_ENQUEUE, hc_enqueue);
+    register_message_handler(USB_IPC_HC_START_TRANSACTION,
+                             hc_start_transaction);
+    register_message_handler(USB_IPC_HC_WAIT_TRANSACTION, hc_wait_transaction);
+    register_message_handler(USB_IPC_HC_DESTROY_TRANSACTION,
+                             hc_destroy_transaction);
+
+    register_message_handler(USB_IPC_RH_PORT_DISABLE, rh_port_disable);
+    register_message_handler(USB_IPC_RH_PORT_RESET_ENABLE,
+                             rh_port_reset_enable);
+    register_message_handler(USB_IPC_RH_PORT_STATUS, rh_port_status);
+}
+
+
+RPC(hc_create_transaction)
+{
+    struct usb_ipc_hc_create_transaction_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct usb_hc* hc = hc_list[par->hc];
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->cdi->dev.driver,
+                                         struct cdi_usb_hcd, drv);
+    struct transaction* ta = malloc(sizeof(*ta));
+
+    *ta = (struct transaction){
+        .ta             = hcd->create_transaction(hc->cdi, &par->target),
+        .transmissions  = list_create()
+    };
+
+    size_t idx;
+    for (idx = hc->transactions_first_free;
+         idx < hc->transactions_size && hc->transactions[idx]; idx++);
+
+    if (idx >= hc->transactions_size) {
+        size_t old_ts = hc->transactions_size;
+        hc->transactions_size = (hc->transactions_size + 4) * 2 / 3;
+        hc->transactions = realloc(hc->transactions,
+                                   hc->transactions_size
+                                   * sizeof(*hc->transactions));
+        memset(hc->transactions + old_ts, 0,
+               (hc->transactions_size - old_ts) * sizeof(*hc->transactions));
+    }
+    hc->transactions[idx] = ta;
+    hc->transactions_first_free = idx + 1;
+
+    cdi_usb_hc_transaction_t ipc_ta = (cdi_usb_hc_transaction_t)(uintptr_t)idx;
+    rpc_send_response(src, corr_id, sizeof(ipc_ta), (char*) &ipc_ta);
+}
+
+
+RPC(hc_enqueue)
+{
+    struct usb_ipc_hc_enqueue_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct usb_hc* hc = hc_list[par->hc];
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->cdi->dev.driver,
+                                         struct cdi_usb_hcd, drv);
+
+    if ((uintptr_t)par->trans.ta > hc->transactions_size ||
+        !hc->transactions[(uintptr_t)par->trans.ta])
+    {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct transaction* ta = hc->transactions[(uintptr_t)par->trans.ta];
+
+    if (ta->started) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct transmission* tm = malloc(sizeof(*tm));
+    *tm = (struct transmission) {
+        .shmid      = par->shm,
+        .mapping    = open_shared_memory(par->shm)
+    };
+
+    par->trans.ta = ta->ta;
+    par->trans.result = tm->mapping;
+    par->trans.buffer = par->trans.result + 1;
+
+    hcd->enqueue(hc->cdi, &par->trans);
+
+    list_push(ta->transmissions, tm);
+
+    rpc_int_resp(0);
+}
+
+
+RPC(hc_start_transaction)
+{
+    struct usb_ipc_hc_start_transaction_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct usb_hc* hc = hc_list[par->hc];
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->cdi->dev.driver,
+                                         struct cdi_usb_hcd, drv);
+
+    if ((uintptr_t)par->ta > hc->transactions_size ||
+        !hc->transactions[(uintptr_t)par->ta])
+    {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct transaction* ta = hc->transactions[(uintptr_t)par->ta];
+    ta->started = true;
+
+    hcd->start_transaction(hc->cdi, ta->ta);
+
+    rpc_int_resp(0);
+}
+
+
+RPC(hc_wait_transaction)
+{
+    struct usb_ipc_hc_wait_transaction_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct usb_hc* hc = hc_list[par->hc];
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->cdi->dev.driver,
+                                         struct cdi_usb_hcd, drv);
+
+    if ((uintptr_t)par->ta > hc->transactions_size ||
+        !hc->transactions[(uintptr_t)par->ta])
+    {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct transaction* ta = hc->transactions[(uintptr_t)par->ta];
+
+    if (!ta->started) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    hcd->wait_transaction(hc->cdi, ta->ta);
+
+    struct transmission* tm;
+    while ((tm = list_pop(ta->transmissions))) {
+        close_shared_memory(tm->shmid);
+        free(tm);
+    }
+
+    rpc_int_resp(0);
+}
+
+
+RPC(hc_destroy_transaction)
+{
+    struct usb_ipc_hc_wait_transaction_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct usb_hc* hc = hc_list[par->hc];
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->cdi->dev.driver,
+                                         struct cdi_usb_hcd, drv);
+
+    if ((uintptr_t)par->ta > hc->transactions_size ||
+        !hc->transactions[(uintptr_t)par->ta])
+    {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct transaction* ta = hc->transactions[(uintptr_t)par->ta];
+
+    hcd->destroy_transaction(hc->cdi, ta->ta);
+
+    hc->transactions[(uintptr_t)par->ta] = NULL;
+    if (hc->transactions_first_free > (uintptr_t)par->ta) {
+        hc->transactions_first_free = (uintptr_t)par->ta;
+    }
+
+    struct transmission* tm;
+    while ((tm = list_pop(ta->transmissions))) {
+        close_shared_memory(tm->shmid);
+        free(tm);
+    }
+
+    list_destroy(ta->transmissions);
+    free(ta);
+
+    rpc_int_resp(0);
+}
+
+
+RPC(rh_port_disable)
+{
+    struct usb_ipc_hc_port_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct cdi_usb_hc* hc = hc_list[par->hc]->cdi;
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->dev.driver, struct cdi_usb_hcd,
+                                         drv);
+
+    hcd->rh_drv.port_disable(&hc->rh, par->index);
+
+    rpc_int_resp(0);
+}
+
+
+RPC(rh_port_reset_enable)
+{
+    struct usb_ipc_hc_port_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_int_resp(0);
+        return;
+    }
+
+    struct cdi_usb_hc* hc = hc_list[par->hc]->cdi;
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->dev.driver, struct cdi_usb_hcd,
+                                         drv);
+
+    hcd->rh_drv.port_reset_enable(&hc->rh, par->index);
+
+    rpc_int_resp(0);
+}
+
+
+RPC(rh_port_status)
+{
+    struct usb_ipc_hc_port_param* par = data;
+    if (length != sizeof(*par)) {
+        rpc_send_dword_response(src, corr_id, 0);
+        return;
+    }
+
+    if (par->hc < 0 || par->hc > (int)hc_list_elements || !hc_list[par->hc]) {
+        rpc_send_dword_response(src, corr_id, 0);
+        return;
+    }
+
+    struct cdi_usb_hc* hc = hc_list[par->hc]->cdi;
+    struct cdi_usb_hcd* hcd = CDI_UPCAST(hc->dev.driver, struct cdi_usb_hcd,
+                                         drv);
+
+    rpc_send_dword_response(src, corr_id,
+                            hcd->rh_drv.port_status(&hc->rh, par->index));
+}
+
+
+void cdi_osdep_provide_hc(struct cdi_usb_hc* cdi_hc);
+
+void cdi_osdep_provide_hc(struct cdi_usb_hc* cdi_hc)
+{
+    struct usb_hc* hc = calloc(1, sizeof(*hc));
+    hc->cdi = cdi_hc;
+
+    set_up_hc_ipc();
+
+    if (hc_list_size == hc_list_elements) {
+        size_t old_hls = hc_list_size;
+        hc_list_size = (hc_list_size + 4) * 2 / 3;
+        hc_list = realloc(hc_list, hc_list_size * sizeof(*hc_list));
+        memset(hc_list + old_hls, 0,
+               (hc_list_size - old_hls) * sizeof(*hc_list));
+    }
+
+    int id = hc_list_elements;
+    hc_list[hc_list_elements++] = hc;
+
+    static pid_t usb_pid;
+    if (!usb_pid) {
+        servmgr_need("usb");
+        usb_pid = init_service_get("usb");
+    }
+
+    struct usb_ipc_hc_found_param par;
+
+    strncpy(par.drv_name, cdi_hc->dev.driver->name, 31);
+    par.drv_name[31] = 0;
+
+    strncpy(par.hc_name, cdi_hc->dev.name, 31);
+    par.hc_name[31] = 0;
+
+    par.id = id;
+    par.rh = cdi_hc->rh;
+
+    rpc_get_int(usb_pid, USB_IPC_HC_FOUND, sizeof(par), (char*) &par);
+}
diff --git a/src/modules/include/usb-ipc.h b/src/modules/include/usb-ipc.h
new file mode 100644
index 0000000..ae6ccf0
--- /dev/null
+++ b/src/modules/include/usb-ipc.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2015 Max Reitz
+ *
+ * This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+ */
+
+#ifndef _USB_IPC_H
+#define _USB_IPC_H
+
+#include <stdint.h>
+
+#include <cdi/usb.h>
+#include <cdi/usb_hcd.h>
+
+
+// Provided by HCDs
+#define USB_IPC_HC_CREATE_TRANSACTION   "HCCRTTA"
+#define USB_IPC_HC_ENQUEUE              "HCNQXFR"
+#define USB_IPC_HC_START_TRANSACTION    "HCSTTTA"
+#define USB_IPC_HC_WAIT_TRANSACTION     "HCWAITA"
+#define USB_IPC_HC_DESTROY_TRANSACTION  "HCDTYTA"
+
+#define USB_IPC_RH_PORT_DISABLE         "RHPRTDN"
+#define USB_IPC_RH_PORT_RESET_ENABLE    "RHPRTUP"
+#define USB_IPC_RH_PORT_STATUS          "RHPRTST"
+
+
+struct usb_ipc_hc_create_transaction_param {
+    int hc;
+    struct cdi_usb_hc_ep_info target;
+};
+
+struct usb_ipc_hc_enqueue_param {
+    int hc;
+    // @buffer and @result are ignored; @result is put at the start of the SHM
+    // provided, @buffer has to be placed directly afterwards
+    struct cdi_usb_hc_transmission trans;
+    uint32_t shm;
+};
+
+struct usb_ipc_hc_start_transaction_param {
+    int hc;
+    cdi_usb_hc_transaction_t ta;
+};
+
+struct usb_ipc_hc_wait_transaction_param {
+    int hc;
+    cdi_usb_hc_transaction_t ta;
+};
+
+struct usb_ipc_hc_destroy_transaction_param {
+    int hc;
+    cdi_usb_hc_transaction_t ta;
+};
+
+struct usb_ipc_hc_port_param {
+    int hc, index;
+};
+
+
+// Provided by the bus driver
+#define USB_IPC_HC_FOUND                "HCFOUND"
+
+#define USB_IPC_GET_ENDPOINT_DESCRIPTOR "GEPDESC"
+
+#define USB_IPC_CONTROL_TRANSFER        "CTRLXFR"
+#define USB_IPC_BULK_TRANSFER           "BULKXFR"
+
+
+struct usb_ipc_hc_found_param {
+    char drv_name[32], hc_name[32];
+    int id;
+    struct cdi_usb_hub rh;
+};
+
+struct usb_ipc_get_endpoint_descriptor_param {
+    int dev, ep;
+};
+
+struct usb_ipc_control_transfer_param {
+    int dev, ep;
+    struct cdi_usb_setup_packet setup;
+    uint32_t shm; // @buffer
+};
+
+struct usb_ipc_bulk_transfer_param {
+    int dev, ep;
+    size_t size;
+    uint32_t shm; // @buffer
+};
+
+
+// Provided by device drivers
+#define USB_IPC_DEVICE_FOUND    "USBPLUG"
+
+
+// The parameter for device_found() is a struct cdi_usb_device.
+
+#endif
-- 
2.6.3