diff --git a/include/vcml/models/usb/device.h b/include/vcml/models/usb/device.h index da064d36..c44d0ed0 100644 --- a/include/vcml/models/usb/device.h +++ b/include/vcml/models/usb/device.h @@ -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& args, ostream& os); diff --git a/include/vcml/models/usb/hostdev.h b/include/vcml/models/usb/hostdev.h index 92f257fc..d9223070 100644 --- a/include/vcml/models/usb/hostdev.h +++ b/include/vcml/models/usb/hostdev.h @@ -32,13 +32,22 @@ class hostdev : public device 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 hostbus; property hostaddr; usb_target_socket usb_in; - hostdev(const sc_module_name& nm); + 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); diff --git a/src/vcml/models/usb/device.cpp b/src/vcml/models/usb/device.cpp index 8c257c43..72e1c3ff 100644 --- a/src/vcml/models/usb/device.cpp +++ b/src/vcml/models/usb/device.cpp @@ -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) { diff --git a/src/vcml/models/usb/drive.cpp b/src/vcml/models/usb/drive.cpp index 787f85ad..95c6a88a 100644 --- a/src/vcml/models/usb/drive.cpp +++ b/src/vcml/models/usb/drive.cpp @@ -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; } @@ -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; } diff --git a/src/vcml/models/usb/hostdev_libusb.cpp b/src/vcml/models/usb/hostdev_libusb.cpp index f16e642e..a43ed255 100644 --- a/src/vcml/models/usb/hostdev_libusb.cpp +++ b/src/vcml/models/usb/hostdev_libusb.cpp @@ -9,7 +9,6 @@ ******************************************************************************/ #include "vcml/models/usb/hostdev.h" -#include "vcml/core/version.h" #include @@ -50,42 +49,170 @@ struct libusb { } }; -hostdev::hostdev(const sc_module_name& nm): +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(), - hostbus("hostbus", 0), - hostaddr("hostaddr", 0), + m_ifs(), + hostbus("hostbus", bus), + hostaddr("hostaddr", addr), usb_in("usb_in") { - m_device = libusb::instance().find_device(hostbus, hostaddr); - VCML_ERROR_ON(!m_device, "no usb device on bus %u at address %u", - hostbus.get(), hostaddr.get()); - int r = libusb_open(m_device, &m_handle); - VCML_ERROR_ON(r, "Failed to open USB device: %s", libusb_strerror(r)); + 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) { - return USB_RESULT_NACK; + 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) { - return USB_RESULT_NACK; + if (!m_handle) + return USB_RESULT_NODEV; + + u8* p = const_cast(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) { - default: + 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(); } diff --git a/src/vcml/models/usb/hostdev_nolibusb.cpp b/src/vcml/models/usb/hostdev_nolibusb.cpp index 19ff9b9e..696a655c 100644 --- a/src/vcml/models/usb/hostdev_nolibusb.cpp +++ b/src/vcml/models/usb/hostdev_nolibusb.cpp @@ -9,20 +9,28 @@ ******************************************************************************/ #include "vcml/models/usb/hostdev.h" -#include "vcml/core/version.h" namespace vcml { namespace usb { -hostdev::hostdev(const sc_module_name& nm): +void hostdev::init_device() { + // nothing to do +} + +hostdev::hostdev(const sc_module_name& nm, u32 bus, u32 addr): device(nm, device_desc()), m_device(), m_handle(), - hostbus("hostbus", 0), - hostaddr("hostaddr", 0), + m_ifs(), + hostbus("hostbus", bus), + hostaddr("hostaddr", addr), usb_in("usb_in") { - (void)m_device; - (void)m_handle; + if (hostbus > 0 || hostaddr > 0) + log_warn("USB host devices not supported (missing libusb)"); +} + +usb_result hostdev::set_config(int config) { + return USB_RESULT_NODEV; } hostdev::~hostdev() { @@ -30,20 +38,16 @@ hostdev::~hostdev() { } usb_result hostdev::get_data(u32 ep, u8* data, size_t len) { - return USB_RESULT_NACK; + return USB_RESULT_NODEV; } usb_result hostdev::set_data(u32 ep, const u8* data, size_t len) { - return USB_RESULT_NACK; + return USB_RESULT_NODEV; } usb_result hostdev::handle_control(u16 req, u16 val, u16 idx, u8* data, size_t length) { - switch (req) { - case USB_REQ_OUT | USB_REQ_DEVICE | USB_REQ_SET_ADDRESS: - default: - return device::handle_control(req, val, idx, data, length); - } + return USB_RESULT_NODEV; } void hostdev::usb_reset_device() { diff --git a/src/vcml/models/usb/keyboard.cpp b/src/vcml/models/usb/keyboard.cpp index b8e5d43e..98448163 100644 --- a/src/vcml/models/usb/keyboard.cpp +++ b/src/vcml/models/usb/keyboard.cpp @@ -304,7 +304,7 @@ usb_result keyboard::set_report(u8* data, size_t length) { } usb_result keyboard::get_data(u32 ep, u8* data, size_t len) { - if (ep != 2) { + if (ep != 1) { log_error("invalid endpoint contacted: %u", ep); return USB_RESULT_NACK; } diff --git a/src/vcml/models/usb/xhci.cpp b/src/vcml/models/usb/xhci.cpp index 35141e45..ca10287d 100644 --- a/src/vcml/models/usb/xhci.cpp +++ b/src/vcml/models/usb/xhci.cpp @@ -1015,11 +1015,12 @@ u32 xhci::handle_transmit(u32 slotid, u32 epid, trb& cmd) { u64 addr = cmd.parameter; u32 size = get_trb_data_length(cmd); + u32 epno = (epid + 1) / 2; for (u32 off = 0; off < size; off += ep->max_psize) { vector buf(min(size - off, ep->max_psize)); if (dirin) { - auto packet = usb_packet_in(slotid, epid, buf.data(), buf.size()); + auto packet = usb_packet_in(slotid, epno, buf.data(), buf.size()); usb_out[slot->port].send(packet); if (failed(packet)) return usb_packet_ccode(packet); @@ -1035,7 +1036,7 @@ u32 xhci::handle_transmit(u32 slotid, u32 epid, trb& cmd) { return TRB_CC_DATA_BUFFER_ERROR; } - auto packet = usb_packet_out(slotid, epid, buf.data(), buf.size()); + auto packet = usb_packet_out(slotid, epno, buf.data(), buf.size()); usb_out[slot->port].send(packet); if (failed(packet)) return usb_packet_ccode(packet); diff --git a/test/models/xhci.cpp b/test/models/xhci.cpp index a61d6837..a30d007e 100644 --- a/test/models/xhci.cpp +++ b/test/models/xhci.cpp @@ -20,6 +20,7 @@ class xhci_test : public test_base usb::keyboard keyboard2; usb::keyboard keyboard3; usb::drive drive2; + usb::hostdev hostdev; tlm_initiator_socket out; gpio_target_socket irq; @@ -32,11 +33,13 @@ class xhci_test : public test_base keyboard2("keyboard2"), keyboard3("keyboard3"), drive2("drive2"), + hostdev("hostdev"), out("out"), irq("irq") { xhci.usb_out[0].bind(keyboard2.usb_in); xhci.usb_out[1].bind(keyboard3.usb_in); xhci.usb_out[2].bind(drive2.usb_in); + xhci.usb_out[3].bind(hostdev.usb_in); bus.bind(mem.in, 0, 0xfff); bus.bind(xhci.in, 0x1000, 0x1fff); @@ -58,6 +61,7 @@ class xhci_test : public test_base EXPECT_STREQ(keyboard2.kind(), "vcml::usb::keyboard"); EXPECT_STREQ(keyboard3.kind(), "vcml::usb::keyboard"); EXPECT_STREQ(drive2.kind(), "vcml::usb::drive"); + EXPECT_STREQ(hostdev.kind(), "vcml::usb::hostdev"); } void test_capabilities() {