diff --git a/CMakeLists.txt b/CMakeLists.txt index f3ea347d..36d574bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -49,6 +50,24 @@ endif() if(VCML_USE_SOCKETCAN) check_include_file("linux/can/raw.h" SOCKETCAN_FOUND) endif() +if(VCML_USE_USB) + find_package(PkgConfig QUIET) + pkg_check_modules(PKGCFG_LIBUSB QUIET libusb-1.0) + find_path(LIBUSB_INCLUDE_DIRS NAMES "libusb.h" + HINTS $ENV{LIBUSB_HOME}/include ${PKGCFG_LIBUSB_INCLUDE_DIRS}) + + find_library(LIBUSB_LIBRARIES NAMES "usb-1.0" + HINTS $ENV{LIBUSB_HOME}/lib ${PKGCFG_LIBUSB_LIBRARY_DIRS}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibUSB + REQUIRED_VARS LIBUSB_INCLUDE_DIRS LIBUSB_LIBRARIES + VERSION_VAR PKGCFG_LIBUSB_VERSION) + mark_as_advanced(LIBUSB_INCLUDE_DIRS LIBUSB_LIBRARIES) + message(DEBUG "LIBUSB_FOUND " ${LIBUSB_FOUND}) + message(DEBUG "LIBUSB_INCLUDE_DIRS " ${LIBUSB_INCLUDE_DIRS}) + message(DEBUG "LIBUSB_LIBRARIES " ${LIBUSB_LIBRARIES}) +endif() set(src ${CMAKE_CURRENT_SOURCE_DIR}/src) set(inc ${CMAKE_CURRENT_SOURCE_DIR}/include) @@ -281,6 +300,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(FATAL_ERROR "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) diff --git a/include/vcml.h b/include/vcml.h index 9e1729dc..d98ba1d5 100644 --- a/include/vcml.h +++ b/include/vcml.h @@ -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" diff --git a/include/vcml/models/usb/hostdev.h b/include/vcml/models/usb/hostdev.h new file mode 100644 index 00000000..92f257fc --- /dev/null +++ b/include/vcml/models/usb/hostdev.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * * + * 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; + +public: + property hostbus; + property hostaddr; + + usb_target_socket usb_in; + + hostdev(const sc_module_name& nm); + 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 diff --git a/src/vcml/models/usb/hostdev_libusb.cpp b/src/vcml/models/usb/hostdev_libusb.cpp new file mode 100644 index 00000000..f16e642e --- /dev/null +++ b/src/vcml/models/usb/hostdev_libusb.cpp @@ -0,0 +1,97 @@ +/****************************************************************************** + * * + * 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 "vcml/core/version.h" + +#include + +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; + } +}; + +hostdev::hostdev(const sc_module_name& nm): + device(nm, device_desc()), + m_device(), + m_handle(), + hostbus("hostbus", 0), + hostaddr("hostaddr", 0), + 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)); +} + +hostdev::~hostdev() { + if (m_device) + libusb_unref_device(m_device); +} + +usb_result hostdev::get_data(u32 ep, u8* data, size_t len) { + return USB_RESULT_NACK; +} + +usb_result hostdev::set_data(u32 ep, const u8* data, size_t len) { + return USB_RESULT_NACK; +} + +usb_result hostdev::handle_control(u16 req, u16 val, u16 idx, u8* data, + size_t length) { + switch (req) { + default: + return device::handle_control(req, val, idx, data, length); + } +} + +void hostdev::usb_reset_device() { + device::usb_reset_device(); +} + +VCML_EXPORT_MODEL(vcml::usb::hostdev, name, args) { + return new hostdev(name); +} + +} // namespace usb +} // namespace vcml diff --git a/src/vcml/models/usb/hostdev_nolibusb.cpp b/src/vcml/models/usb/hostdev_nolibusb.cpp new file mode 100644 index 00000000..b54e8316 --- /dev/null +++ b/src/vcml/models/usb/hostdev_nolibusb.cpp @@ -0,0 +1,57 @@ +/****************************************************************************** + * * + * 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 "vcml/core/version.h" + +#include + +namespace vcml { +namespace usb { + +hostdev::hostdev(const sc_module_name& nm): + device(nm, device_desc()), + m_device(), + m_handle(), + hostbus("hostbus", 0), + hostaddr("hostaddr", 0), + usb_in("usb_in") { +} + +hostdev::~hostdev() { + // nothing to do +} + +usb_result hostdev::get_data(u32 ep, u8* data, size_t len) { + return USB_RESULT_NACK; +} + +usb_result hostdev::set_data(u32 ep, const u8* data, size_t len) { + return USB_RESULT_NACK; +} + +usb_result hostdev::handle_control(u16 req, u16 val, u16 idx, u8* data, + size_t length) { + switch (req) { + default: + return device::handle_control(req, val, idx, data, length); + } +} + +void hostdev::usb_reset_device() { + device::usb_reset_device(); +} + +VCML_EXPORT_MODEL(vcml::usb::hostdev, name, args) { + return new hostdev(name); +} + +} // namespace usb +} // namespace vcml