Tidying low-level debug tool driver code:
- Use automatic objects for libusb/hidapi resources, where possible (to reduce manual resource management) - Removed unused/redundant code - Tidied HidInterface class - Tidied debug tool initialisation code - Other bits of tidying
This commit is contained in:
@@ -9,65 +9,71 @@ namespace Bloom::Usb
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
HidInterface::HidInterface(std::uint8_t interfaceNumber, std::uint16_t vendorId, std::uint16_t productId)
|
||||
: interfaceNumber(interfaceNumber)
|
||||
, vendorId(vendorId)
|
||||
, productId(productId)
|
||||
{}
|
||||
|
||||
void HidInterface::init() {
|
||||
if (this->libUsbDevice == nullptr) {
|
||||
throw DeviceInitializationFailure("Cannot initialise interface without libusb device pointer.");
|
||||
}
|
||||
::hid_init();
|
||||
::hid_device* hidDevice = nullptr;
|
||||
|
||||
hid_init();
|
||||
hid_device* hidDevice = nullptr;
|
||||
|
||||
std::string hidInterfacePath = this->getDevicePathByInterfaceNumber(this->getNumber());
|
||||
const auto hidInterfacePath = this->getHidDevicePath();
|
||||
Logger::debug("HID device path: " + hidInterfacePath);
|
||||
|
||||
if ((hidDevice = hid_open_path(hidInterfacePath.c_str())) == nullptr) {
|
||||
if ((hidDevice = ::hid_open_path(hidInterfacePath.c_str())) == nullptr) {
|
||||
throw DeviceInitializationFailure("Failed to open HID device via hidapi.");
|
||||
}
|
||||
|
||||
if (hidDevice->input_ep_max_packet_size < 1) {
|
||||
this->hidDevice.reset(hidDevice);
|
||||
|
||||
if (this->hidDevice->input_ep_max_packet_size < 1) {
|
||||
throw DeviceInitializationFailure(
|
||||
"Invalid max packet size for USB endpoint, on interface "
|
||||
+ std::to_string(this->getNumber())
|
||||
+ std::to_string(this->interfaceNumber)
|
||||
);
|
||||
}
|
||||
|
||||
this->setHidDevice(hidDevice);
|
||||
this->setInputReportSize(static_cast<std::size_t>(hidDevice->input_ep_max_packet_size));
|
||||
this->libUsbDeviceHandle = hidDevice->device_handle;
|
||||
this->initialised = true;
|
||||
this->inputReportSize = static_cast<std::size_t>(this->hidDevice->input_ep_max_packet_size);
|
||||
}
|
||||
|
||||
void HidInterface::close() {
|
||||
auto* hidDevice = this->getHidDevice();
|
||||
|
||||
if (hidDevice != nullptr) {
|
||||
this->libUsbDeviceHandle = nullptr;
|
||||
hid_close(hidDevice);
|
||||
// hid_close() releases the interface
|
||||
this->claimed = false;
|
||||
}
|
||||
|
||||
hid_exit();
|
||||
Interface::close();
|
||||
this->hidDevice.reset();
|
||||
::hid_exit();
|
||||
}
|
||||
|
||||
std::vector<unsigned char> HidInterface::read(unsigned int timeout) {
|
||||
std::vector<unsigned char> output;
|
||||
auto readSize = this->getInputReportSize();
|
||||
std::vector<unsigned char> HidInterface::read(std::optional<std::chrono::milliseconds> timeout) {
|
||||
auto output = std::vector<unsigned char>();
|
||||
|
||||
// Attempt to read the first HID report packet, and whatever is left after that.
|
||||
output.resize(readSize);
|
||||
auto transferredByteCount = this->read(output.data(), readSize, timeout);
|
||||
auto totalByteCount = transferredByteCount;
|
||||
const auto readSize = this->inputReportSize;
|
||||
auto transferredByteCount = int(0);
|
||||
auto totalByteCount = std::size_t(0);
|
||||
|
||||
while (transferredByteCount >= readSize) {
|
||||
output.resize(totalByteCount + readSize);
|
||||
do {
|
||||
output.resize(totalByteCount + readSize, 0x00);
|
||||
|
||||
transferredByteCount = this->read(output.data() + totalByteCount, readSize, 1);
|
||||
totalByteCount += transferredByteCount;
|
||||
}
|
||||
transferredByteCount = ::hid_read_timeout(
|
||||
this->hidDevice.get(),
|
||||
output.data() + totalByteCount,
|
||||
readSize,
|
||||
timeout.has_value() ? static_cast<int>(timeout->count()) : -1
|
||||
);
|
||||
|
||||
output.resize(totalByteCount);
|
||||
if (transferredByteCount == -1) {
|
||||
throw DeviceCommunicationFailure("Failed to read from HID device.");
|
||||
}
|
||||
|
||||
if (totalByteCount == 0) {
|
||||
// After the first read, set the timeout to 1 millisecond, as we don't want to wait for subsequent data
|
||||
timeout = std::chrono::milliseconds(1);
|
||||
}
|
||||
|
||||
totalByteCount += static_cast<std::size_t>(transferredByteCount);
|
||||
|
||||
} while (transferredByteCount >= readSize);
|
||||
|
||||
output.resize(totalByteCount, 0x00);
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -89,49 +95,35 @@ namespace Bloom::Usb
|
||||
int transferred = 0;
|
||||
const auto length = buffer.size();
|
||||
|
||||
if ((transferred = hid_write(this->getHidDevice(), buffer.data(), length)) != length) {
|
||||
if ((transferred = ::hid_write(this->hidDevice.get(), buffer.data(), length)) != length) {
|
||||
Logger::debug("Attempted to write " + std::to_string(length)
|
||||
+ " bytes to HID interface. Bytes written: " + std::to_string(transferred));
|
||||
throw DeviceCommunicationFailure("Failed to write data to HID interface.");
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t HidInterface::read(unsigned char* buffer, std::size_t maxLength, unsigned int timeout) {
|
||||
int transferred;
|
||||
|
||||
if ((transferred = hid_read_timeout(
|
||||
this->hidDevice,
|
||||
buffer,
|
||||
maxLength,
|
||||
timeout == 0 ? -1 : static_cast<int>(timeout))
|
||||
) == -1
|
||||
) {
|
||||
throw DeviceCommunicationFailure("Failed to read from HID device.");
|
||||
}
|
||||
|
||||
return static_cast<std::size_t>(transferred);
|
||||
}
|
||||
|
||||
std::string HidInterface::getDevicePathByInterfaceNumber(const std::uint16_t& interfaceNumber) {
|
||||
hid_device_info* hidDeviceInfoList = hid_enumerate(
|
||||
this->getVendorId(),
|
||||
this->getProductId()
|
||||
std::string HidInterface::getHidDevicePath() {
|
||||
const auto hidDeviceInfoList = std::unique_ptr<::hid_device_info, decltype(&::hid_free_enumeration)>(
|
||||
::hid_enumerate(this->vendorId, this->productId),
|
||||
::hid_free_enumeration
|
||||
);
|
||||
|
||||
while (hidDeviceInfoList != nullptr) {
|
||||
if (hidDeviceInfoList->interface_number == interfaceNumber) {
|
||||
auto matchedDevice = std::optional<::hid_device_info*>();
|
||||
|
||||
auto* hidDeviceInfo = hidDeviceInfoList.get();
|
||||
while (hidDeviceInfo != nullptr) {
|
||||
if (hidDeviceInfo->interface_number == this->interfaceNumber) {
|
||||
matchedDevice = hidDeviceInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
hidDeviceInfoList = hidDeviceInfoList->next;
|
||||
hidDeviceInfo = hidDeviceInfoList->next;
|
||||
}
|
||||
|
||||
if (hidDeviceInfoList == nullptr) {
|
||||
if (!matchedDevice.has_value()) {
|
||||
throw DeviceInitializationFailure("Failed to match interface number with HID interface.");
|
||||
}
|
||||
|
||||
auto path = std::string(hidDeviceInfoList->path);
|
||||
hid_free_enumeration(hidDeviceInfoList);
|
||||
return path;
|
||||
return std::string(matchedDevice.value()->path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
|
||||
#include "hidapi.hpp"
|
||||
#include "src/DebugToolDrivers/USB/Interface.hpp"
|
||||
|
||||
namespace Bloom::Usb
|
||||
{
|
||||
@@ -15,35 +17,46 @@ namespace Bloom::Usb
|
||||
* Currently, this interface only supports single-report HID implementations. HID interfaces with
|
||||
* multiple reports will be supported as-and-when we need it.
|
||||
*/
|
||||
class HidInterface: public Interface
|
||||
class HidInterface
|
||||
{
|
||||
public:
|
||||
std::uint8_t interfaceNumber = 0;
|
||||
|
||||
HidInterface(
|
||||
std::uint8_t interfaceNumber,
|
||||
std::uint16_t vendorId,
|
||||
std::uint16_t productId
|
||||
);
|
||||
|
||||
HidInterface(const HidInterface& other) = delete;
|
||||
HidInterface& operator = (const HidInterface& other) = delete;
|
||||
|
||||
HidInterface(HidInterface&& other) = default;
|
||||
HidInterface& operator = (HidInterface&& other) = default;
|
||||
|
||||
std::size_t getInputReportSize() const {
|
||||
return this->inputReportSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims the USB HID interface and obtains a hid_device instance
|
||||
* Obtains a hid_device instance and claims the HID interface on the device.
|
||||
*/
|
||||
void init() override;
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Closes the hid_device and releases any claimed interfaces (via hid_close())
|
||||
* Releases any claimed interfaces and closes the hid_device.
|
||||
*/
|
||||
void close() override;
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Reads as much data as the device has to offer, into a vector.
|
||||
*
|
||||
* If `timeout` is set to 0, this method will block until at least one HID report
|
||||
* packet is received.
|
||||
*
|
||||
* @param timeout
|
||||
*
|
||||
* @return
|
||||
* A vector of the data received from the device.
|
||||
*/
|
||||
std::vector<unsigned char> read(unsigned int timeout = 0);
|
||||
std::vector<unsigned char> read(std::optional<std::chrono::milliseconds> timeout = std::nullopt);
|
||||
|
||||
/**
|
||||
* Writes buffer to HID output endpoint.
|
||||
@@ -52,59 +65,20 @@ namespace Bloom::Usb
|
||||
*/
|
||||
void write(std::vector<unsigned char>&& buffer);
|
||||
|
||||
/**
|
||||
* Resolves a device path from a USB interface number.
|
||||
*
|
||||
* @param interfaceNumber
|
||||
* @return
|
||||
*/
|
||||
std::string getDevicePathByInterfaceNumber(const std::uint16_t& interfaceNumber);
|
||||
|
||||
protected:
|
||||
hid_device* getHidDevice() const {
|
||||
return this->hidDevice;
|
||||
}
|
||||
std::string getHidDevicePath();
|
||||
|
||||
private:
|
||||
/**
|
||||
* The HIDAPI library provides a hid_device data structure to represent a USB HID interface.
|
||||
*
|
||||
* @see hidapi.hpp or the HIDAPI documentation for more on this.
|
||||
*/
|
||||
hid_device* hidDevice = nullptr;
|
||||
using HidDeviceType = std::unique_ptr<::hid_device, decltype(&::hid_close)>;
|
||||
|
||||
HidDeviceType hidDevice = HidDeviceType(nullptr, ::hid_close);
|
||||
|
||||
/**
|
||||
* All HID reports have a fixed report length. This means that every packet
|
||||
* we send or receive to/from an HID endpoint must be equal to the report length in size.
|
||||
*
|
||||
* The default input report size is 64 bytes, but this is overridden at interface initialisation.
|
||||
* @see definition of init() for more on this.
|
||||
* All HID reports have a fixed report length. This means that every packet we send or receive to/from an HID
|
||||
* endpoint must be equal (in size) to the report length.
|
||||
*/
|
||||
std::size_t inputReportSize = 64;
|
||||
|
||||
void setHidDevice(hid_device* hidDevice) {
|
||||
this->hidDevice = hidDevice;
|
||||
}
|
||||
|
||||
void setInputReportSize(const std::size_t& inputReportSize) {
|
||||
this->inputReportSize = inputReportSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a maximum of `maxLength` bytes into `buffer`, from the HID input endpoint.
|
||||
*
|
||||
* Keeping this in the private scope to enforce use of vector<unsigned char>. See read()
|
||||
* method in public scope.
|
||||
*
|
||||
* @TODO: Do we really need this? Why not just have the one that accepts the vector. Review
|
||||
*
|
||||
* @param buffer
|
||||
* @param maxLength
|
||||
* @param timeout
|
||||
*
|
||||
* @return
|
||||
* Number of bytes read.
|
||||
*/
|
||||
std::size_t read(unsigned char* buffer, std::size_t maxLength, unsigned int timeout);
|
||||
std::uint16_t vendorId = 0;
|
||||
std::uint16_t productId = 0;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user