Skip to content

Commit

Permalink
Adding usb host device support
Browse files Browse the repository at this point in the history
  • Loading branch information
janweinstock committed Apr 3, 2024
1 parent ff8876f commit f88f824
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Cache SystemC
id: cache-systemc
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{github.workspace}}/systemc
key: cache-systemc-${{matrix.systemc}}-${{runner.arch}}-${{runner.name}}-clang-tidy
Expand Down
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ option(VCML_USE_SLIRP "Use SLIRP for networking" ON)
option(VCML_USE_TAP "Use TAP for networking" ON)
option(VCML_USE_LUA "Use LUA for scripting" ON)
option(VCML_USE_SOCKETCAN "Use CAN sockets" ON)
option(VCML_USE_USB "Use LibUSB for host USB devices" ON)
option(VCML_BUILD_TESTS "Build unit tests" OFF)
option(VCML_BUILD_UTILS "Build utility programs" ON)
option(VCML_COVERAGE "Enable generation of code coverage data" OFF)
Expand Down Expand Up @@ -49,6 +50,9 @@ endif()
if(VCML_USE_SOCKETCAN)
check_include_file("linux/can/raw.h" SOCKETCAN_FOUND)
endif()
if(VCML_USE_USB)
find_package(LibUSB)
endif()

set(src ${CMAKE_CURRENT_SOURCE_DIR}/src)
set(inc ${CMAKE_CURRENT_SOURCE_DIR}/include)
Expand Down Expand Up @@ -281,6 +285,17 @@ else()
target_sources(vcml PRIVATE ${src}/vcml/properties/broker_nolua.cpp)
endif()

if(LIBUSB_FOUND)
message(STATUS "Building with LibUSB support")
target_compile_definitions(vcml PRIVATE HAVE_LIBUSB)
target_include_directories(vcml SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIRS})
target_sources(vcml PRIVATE ${src}/vcml/models/usb/hostdev_libusb.cpp)
target_link_libraries(vcml PUBLIC ${LIBUSB_LIBRARIES})
else()
message(STATUS "Building without LibUSB support")
target_sources(vcml PRIVATE ${src}/vcml/models/usb/hostdev_nolibusb.cpp)
endif()

if(VCML_COVERAGE)
target_compile_options(vcml PUBLIC --coverage)
target_link_libraries(vcml PUBLIC -lgcov)
Expand Down
2 changes: 1 addition & 1 deletion cmake
Submodule cmake updated 1 files
+28 −0 FindLibUSB.cmake
1 change: 1 addition & 0 deletions include/vcml.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
#include "vcml/models/usb/device.h"
#include "vcml/models/usb/keyboard.h"
#include "vcml/models/usb/drive.h"
#include "vcml/models/usb/hostdev.h"

#include "vcml/models/pci/device.h"
#include "vcml/models/pci/host.h"
Expand Down
2 changes: 1 addition & 1 deletion include/vcml/models/usb/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class device : public module, public usb_dev_if
usb_result res;
control_state state;
u8* ptr;
u8 buf[1024];
u8 buf[1u << 16];
} m_ep0;

bool cmd_usb_attach(const vector<string>& args, ostream& os);
Expand Down
67 changes: 67 additions & 0 deletions include/vcml/models/usb/hostdev.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/******************************************************************************
* *
* Copyright (C) 2024 MachineWare GmbH *
* All Rights Reserved *
* *
* This is work is licensed under the terms described in the LICENSE file *
* found in the root directory of this source tree. *
* *
******************************************************************************/

#ifndef VCML_USB_HOSTDEV_H
#define VCML_USB_HOSTDEV_H

#include "vcml/core/types.h"
#include "vcml/core/systemc.h"
#include "vcml/core/module.h"
#include "vcml/core/model.h"

#include "vcml/protocols/usb.h"
#include "vcml/models/usb/device.h"

struct libusb_context;
struct libusb_device;
struct libusb_device_handle;

namespace vcml {
namespace usb {

class hostdev : public device
{
private:
struct libusb_device* m_device;
struct libusb_device_handle* m_handle;

struct hostdev_interface {
bool detached;
bool claimed;
} m_ifs[16];

void init_device();
usb_result set_config(int i);

public:
property<u32> hostbus;
property<u32> hostaddr;

usb_target_socket usb_in;

hostdev(const sc_module_name& nm): hostdev(nm, 0, 0) {}
hostdev(const sc_module_name& nm, u32 bus, u32 addr);
virtual ~hostdev();
VCML_KIND(usb::hostdev);

protected:
virtual usb_result get_data(u32 ep, u8* data, size_t len) override;
virtual usb_result set_data(u32 ep, const u8* data, size_t len) override;

virtual usb_result handle_control(u16 req, u16 val, u16 idx, u8* data,
size_t length) override;

virtual void usb_reset_device() override;
};

} // namespace usb
} // namespace vcml

#endif
4 changes: 3 additions & 1 deletion src/vcml/models/usb/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,10 @@ usb_result device::handle_ep0(usb_packet& p) {
m_ep0.len = p.data[6] | (u16)p.data[7] << 8;
m_ep0.pos = 0;

if (m_ep0.len > sizeof(m_ep0.buf))
if ((size_t)m_ep0.len > sizeof(m_ep0.buf)) {
log_warn("control request too big: %hu bytes", m_ep0.len);
return USB_RESULT_BABBLE;
}

usb_result res = USB_RESULT_SUCCESS;
if (m_ep0.req & USB_REQ_IN) {
Expand Down
4 changes: 2 additions & 2 deletions src/vcml/models/usb/drive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ drive::~drive() {
}

usb_result drive::get_data(u32 ep, u8* data, size_t len) {
if (ep != 2) {
if (ep != 1) {
log_warn("invalid input endpoint: %u", ep);
return USB_RESULT_STALL;
}
Expand Down Expand Up @@ -348,7 +348,7 @@ usb_result drive::get_data(u32 ep, u8* data, size_t len) {
}

usb_result drive::set_data(u32 ep, const u8* data, size_t len) {
if (ep != 3) {
if (ep != 2) {
log_warn("invalid output endpoint: %u", ep);
return USB_RESULT_STALL;
}
Expand Down
224 changes: 224 additions & 0 deletions src/vcml/models/usb/hostdev_libusb.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/******************************************************************************
* *
* Copyright (C) 2024 MachineWare GmbH *
* All Rights Reserved *
* *
* This is work is licensed under the terms described in the LICENSE file *
* found in the root directory of this source tree. *
* *
******************************************************************************/

#include "vcml/models/usb/hostdev.h"

#include <libusb.h>

namespace vcml {
namespace usb {

struct libusb {
libusb_context* context;
libusb(): context(nullptr) {
int r = libusb_init(&context);
VCML_ERROR_ON(r < 0, "failed to initialize libusb");
}

~libusb() {
if (context)
libusb_exit(context);
}

static libusb& instance() {
static libusb singleton;
return singleton;
}

libusb_device* find_device(u32 bus, u32 addr) {
libusb_device* found = nullptr;
libusb_device** list = nullptr;
size_t n = libusb_get_device_list(context, &list);
for (size_t i = 0; i < n; i++) {
if (libusb_get_bus_number(list[i]) == bus &&
libusb_get_device_address(list[i]) == addr) {
found = libusb_ref_device(list[i]);
break;
}
}

libusb_free_device_list(list, true);
return found;
}
};

constexpr usb_result usb_result_from_libusb(int r) {
switch (r) {
case LIBUSB_SUCCESS:
return USB_RESULT_SUCCESS;
case LIBUSB_ERROR_NO_DEVICE:
return USB_RESULT_NODEV;
case LIBUSB_ERROR_TIMEOUT:
return USB_RESULT_STALL;
case LIBUSB_ERROR_OVERFLOW:
return USB_RESULT_BABBLE;
case LIBUSB_ERROR_IO:
default:
return USB_RESULT_IOERROR;
}
}

static string libusb_str_desc(libusb_device_handle* h, u16 idx) {
unsigned char buffer[256]{};
libusb_get_string_descriptor_ascii(h, idx, buffer, sizeof(buffer));
return string((char*)buffer);
}

void hostdev::init_device() {
int r = libusb_open(m_device, &m_handle);
VCML_ERROR_ON(r, "Failed to open USB device: %s", libusb_strerror(r));

libusb_device_descriptor desc;
r = libusb_get_device_descriptor(m_device, &desc);
VCML_ERROR_ON(r, "Failed to fetch USB descriptor: %s", libusb_strerror(r));

m_desc.bcd_usb = desc.bcdUSB;
m_desc.device_class = desc.bDeviceClass;
m_desc.device_subclass = desc.bDeviceSubClass;
m_desc.device_protocol = desc.bDeviceProtocol;
m_desc.max_packet_size0 = desc.bMaxPacketSize0;
m_desc.vendor_id = desc.idVendor;
m_desc.product_id = desc.idProduct;
m_desc.bcd_device = desc.bcdDevice;
m_desc.manufacturer = libusb_str_desc(m_handle, desc.iManufacturer);
m_desc.product = libusb_str_desc(m_handle, desc.iProduct);
m_desc.serial_number = libusb_str_desc(m_handle, desc.iSerialNumber);

for (size_t i = 0; i < MWR_ARRAY_SIZE(m_ifs); i++) {
int r = libusb_kernel_driver_active(m_handle, i);
if (r > 0) {
log_debug("detaching kernel driver from interface %zu", i);
r = libusb_detach_kernel_driver(m_handle, i);
VCML_ERROR_ON(r < 0, "libusb_detach_kernel_driver: %s",
libusb_strerror(r));
m_ifs[i].detached = true;
}
}

log_debug("attached to device %04hx:%04hx (%s)", m_desc.vendor_id,
m_desc.product_id, m_desc.product.c_str());
}

usb_result hostdev::set_config(int config) {
if (!m_handle)
return USB_RESULT_NODEV;

for (size_t i = 0; i < MWR_ARRAY_SIZE(m_ifs); i++) {
if (m_ifs[i].claimed) {
libusb_release_interface(m_handle, i);
log_debug("released interface %zu", i);
}

m_ifs[i].claimed = false;
}

int r = libusb_set_configuration(m_handle, config);
VCML_ERROR_ON(r < 0, "libusb_set_config: %s", libusb_strerror(r));

for (size_t i = 0; i < MWR_ARRAY_SIZE(m_ifs); i++) {
if ((r = libusb_claim_interface(m_handle, i)) == 0) {
log_debug("claimed interface %zu", i);
m_ifs[i].claimed = true;
}
}

return USB_RESULT_SUCCESS;
}

hostdev::hostdev(const sc_module_name& nm, u32 bus, u32 addr):
device(nm, device_desc()),
m_device(),
m_handle(),
m_ifs(),
hostbus("hostbus", bus),
hostaddr("hostaddr", addr),
usb_in("usb_in") {
memset(m_ifs, 0, sizeof(m_ifs));
if (hostbus > 0u && hostaddr > 0u) {
m_device = libusb::instance().find_device(hostbus, hostaddr);
if (!m_device) {
log_error("no USB device on bus %u at address %u", hostbus.get(),
hostaddr.get());
}
}

if (m_device)
init_device();
}

hostdev::~hostdev() {
for (size_t i = 0; i < MWR_ARRAY_SIZE(m_ifs); i++) {
if (m_ifs[i].claimed) {
log_debug("releasing interface %zu", i);
libusb_release_interface(m_handle, i);
}

if (m_ifs[i].detached) {
log_debug("re-attaching kernel driver to interface %zu", i);
libusb_attach_kernel_driver(m_handle, i);
}
}

if (m_handle)
libusb_close(m_handle);
if (m_device)
libusb_unref_device(m_device);
}

usb_result hostdev::get_data(u32 ep, u8* data, size_t len) {
if (!m_handle)
return USB_RESULT_NODEV;

int r = libusb_bulk_transfer(m_handle, usb_ep_in(ep), data, len, NULL, 0);
return usb_result_from_libusb(r);
}

usb_result hostdev::set_data(u32 ep, const u8* data, size_t len) {
if (!m_handle)
return USB_RESULT_NODEV;

u8* p = const_cast<u8*>(data);
int r = libusb_bulk_transfer(m_handle, usb_ep_out(ep), p, len, NULL, 0);
return usb_result_from_libusb(r);
}

usb_result hostdev::handle_control(u16 req, u16 val, u16 idx, u8* data,
size_t length) {
if (!m_handle)
return USB_RESULT_NODEV;

switch (req) {
case USB_REQ_OUT | USB_REQ_DEVICE | USB_REQ_SET_ADDRESS:
return device::handle_control(req, val, idx, data, length);
case USB_REQ_OUT | USB_REQ_DEVICE | USB_REQ_SET_CONFIGURATION:
return set_config(val & 0xff);
}

u16 t = req >> 8;
u16 q = req & 0xff;
int r = libusb_control_transfer(m_handle, t, q, val, idx, data, length, 0);
if (r < 0)
return usb_result_from_libusb(r);
return USB_RESULT_SUCCESS;
}

void hostdev::usb_reset_device() {
if (m_handle)
libusb_reset_device(m_handle);

device::usb_reset_device();
}

VCML_EXPORT_MODEL(vcml::usb::hostdev, name, args) {
return new hostdev(name);
}

} // namespace usb
} // namespace vcml
Loading

0 comments on commit f88f824

Please sign in to comment.