-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modernize Hid/Bulk Lists #13622
base: main
Are you sure you want to change the base?
Modernize Hid/Bulk Lists #13622
Changes from 14 commits
3e2b8e9
2355ace
2c2b47f
4f4c424
14f0ea8
7b712fa
4d58f6b
a914b05
c29c42e
8b7c661
7420032
564a318
46cf164
4df2508
18c8ce4
cc182df
60541eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
|
||
#include <libusb.h> | ||
|
||
#include <algorithm> | ||
|
||
#include "controllers/bulk/bulksupported.h" | ||
#include "controllers/defs_controllers.h" | ||
#include "moc_bulkcontroller.cpp" | ||
|
@@ -130,12 +132,10 @@ bool BulkController::matchProductInfo(const ProductInfo& product) { | |
return false; | ||
} | ||
|
||
#if defined(__WINDOWS__) || defined(__APPLE__) | ||
value = product.interface_number.toInt(&ok, 16); | ||
if (!ok || m_interfaceNumber != static_cast<unsigned int>(value)) { | ||
if (!ok || m_interfaceNumber != value) { | ||
return false; | ||
} | ||
#endif | ||
|
||
// Match found | ||
return true; | ||
|
@@ -148,23 +148,18 @@ int BulkController::open() { | |
} | ||
|
||
/* Look up endpoint addresses in supported database */ | ||
int i; | ||
for (i = 0; bulk_supported[i].vendor_id; ++i) { | ||
if ((bulk_supported[i].vendor_id == m_vendorId) && | ||
(bulk_supported[i].product_id == m_productId)) { | ||
m_inEndpointAddr = bulk_supported[i].in_epaddr; | ||
m_outEndpointAddr = bulk_supported[i].out_epaddr; | ||
#if defined(__WINDOWS__) || defined(__APPLE__) | ||
m_interfaceNumber = bulk_supported[i].interface_number; | ||
#endif | ||
break; | ||
} | ||
} | ||
|
||
if (bulk_supported[i].vendor_id == 0) { | ||
const bulk_support_lookup* pDevice = std::find_if( | ||
std::cbegin(bulk_supported), std::cend(bulk_supported), [this](const auto& dev) { | ||
return dev.key.vendor_id == m_vendorId && dev.key.product_id == m_productId; | ||
}); | ||
if (pDevice == std::cend(bulk_supported)) { | ||
qCWarning(m_logBase) << "USB Bulk device" << getName() << "unsupported"; | ||
return -1; | ||
} | ||
m_inEndpointAddr = pDevice->endpoints.in_epaddr; | ||
m_outEndpointAddr = pDevice->endpoints.out_epaddr; | ||
m_interfaceNumber = pDevice->endpoints.interface_number; | ||
|
||
// XXX: we should enumerate devices and match vendor, product, and serial | ||
if (m_phandle == nullptr) { | ||
|
@@ -176,31 +171,18 @@ int BulkController::open() { | |
qCWarning(m_logBase) << "Unable to open USB Bulk device" << getName(); | ||
return -1; | ||
} | ||
|
||
#if defined(__WINDOWS__) || defined(__APPLE__) | ||
if (m_interfaceNumber && libusb_kernel_driver_active(m_phandle, m_interfaceNumber) == 1) { | ||
qCDebug(m_logBase) << "Found a driver active for" << getName(); | ||
if (libusb_detach_kernel_driver(m_phandle, 0) == 0) | ||
qCDebug(m_logBase) << "Kernel driver detached for" << getName(); | ||
else { | ||
qCWarning(m_logBase) << "Couldn't detach kernel driver for" << getName(); | ||
libusb_close(m_phandle); | ||
return -1; | ||
} | ||
if (libusb_set_auto_detach_kernel_driver(m_phandle, true) == LIBUSB_ERROR_NOT_SUPPORTED) { | ||
qCDebug(m_logBase) << "unable to automatically detach kernel driver for" << getName(); | ||
} | ||
|
||
if (m_interfaceNumber) { | ||
int ret = libusb_claim_interface(m_phandle, m_interfaceNumber); | ||
if (ret < 0) { | ||
qCWarning(m_logBase) << "Cannot claim interface for" << getName() | ||
<< ":" << libusb_error_name(ret); | ||
libusb_close(m_phandle); | ||
return -1; | ||
} else { | ||
qCDebug(m_logBase) << "Claimed interface for" << getName(); | ||
} | ||
if (int ret = libusb_claim_interface(m_phandle, m_interfaceNumber); ret < 0) { | ||
qCWarning(m_logBase) << "Cannot claim interface for" << getName() | ||
<< ":" << libusb_error_name(ret); | ||
libusb_close(m_phandle); | ||
return -1; | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be moved to the inner scope There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. whoops of course. fixed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. whoops, of course. done |
||
qCDebug(m_logBase) << "Claimed interface for" << getName(); | ||
} | ||
#endif | ||
|
||
setOpen(true); | ||
startEngine(); | ||
|
@@ -255,15 +237,10 @@ int BulkController::close() { | |
stopEngine(); | ||
|
||
// Close device | ||
#if defined(__WINDOWS__) || defined(__APPLE__) | ||
if (m_interfaceNumber) { | ||
int ret = libusb_release_interface(m_phandle, m_interfaceNumber); | ||
if (ret < 0) { | ||
qCWarning(m_logBase) << "Cannot release interface for" << getName() | ||
<< ":" << libusb_error_name(ret); | ||
} | ||
if (int ret = libusb_release_interface(m_phandle, m_interfaceNumber); ret < 0) { | ||
qCWarning(m_logBase) << "Cannot release interface for" << getName() | ||
<< ":" << libusb_error_name(ret); | ||
} | ||
#endif | ||
qCInfo(m_logBase) << " Closing device"; | ||
libusb_close(m_phandle); | ||
m_phandle = nullptr; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,13 +72,12 @@ class BulkController : public Controller { | |
|
||
// Local copies of things we need from desc | ||
|
||
unsigned short m_vendorId; | ||
unsigned short m_productId; | ||
unsigned char m_inEndpointAddr; | ||
unsigned char m_outEndpointAddr; | ||
#if defined(__WINDOWS__) || defined(__APPLE__) | ||
unsigned int m_interfaceNumber; | ||
#endif | ||
std::uint16_t m_vendorId; | ||
std::uint16_t m_productId; | ||
std::uint8_t m_inEndpointAddr; | ||
std::uint8_t m_outEndpointAddr; | ||
std::uint8_t m_interfaceNumber; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should become an int, to match the usbapi type and to allow us to retain the 2.5 behaviour for the devices where we don't know the interface number by setting it to -1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, when starting on a plan surface. Libusb has decided to use -1 for invalid, so we should IMHO not introduce another concept. The API will just work with -1 and has internal checks, no need for any has_value() checks, in our code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was not able to find that definition. Can you link it to me?
Well, if we want to preserve how it works currently though, we need to check for -1 anyways, so we might as well check for has_value. I'm not sure if passing -1 deliberately is a good choice because it'll only make it more complicated to then handle the error (because we need to then distinguish from an error that occurred because we passed in -1 or we actually did pass in a garbage value we didn't account for). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is just used literally in libusb like here: The only related macro is USB_MAXINTERFACES There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I could tell is that @DaSchauer wants to introduce a sentinel value (-1 currently) that determines whether we should claim the interface at all. At least the current code only claimed an interface when it was non-zero. Whether that is correct is dubious so I just implemented it to claim it unconditionally. While I don't object to that outright (it worked until now apparently, does anybody know better what to?), I insist on using a typesafe sentinel ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyways, I think we should try the state as currently implemented. None of us has a hercules controller anyways so its not like we can easily verify that this breaks something. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is impossible to transfer data to or from an USB device without specifiing the interface (there might be a default, but it must be specified somehow). This is mandatory by the standard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Claiming interfaces has been introduced recently in this PR #13008. The libusb implementation is implemented in a way that a handle can claim one interface or more. Transferring data works with the endpoint address only, without specifying the interface. So I assume that the endpoint address alone is sufficient for transferring the data. Claiming the interface seems to be only an exclusive access thing. Searching the forum has revealed that the interface number is not always 0.
I am afraid this is wrong. My conclusion is that claiming the interface 0 here for all devices is also wrong, since we don't have the knowledge which interface to claim. As a workaround we may restore the pre #13008 by not claiming the interface indicated by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, if we don't know the correct interface number, we shouldn't assume that the device use 0. |
||
|
||
QString m_manufacturer; | ||
QString m_product; | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,18 +1,28 @@ | ||||
#pragma once | ||||
|
||||
#include <cstdint> | ||||
|
||||
struct bulk_device_id { | ||||
std::uint16_t vendor_id; | ||||
daschuer marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
std::uint16_t product_id; | ||||
}; | ||||
|
||||
// A list of supported USB bulk devices | ||||
|
||||
#pragma once | ||||
struct bulk_device_endpoints { | ||||
std::uint8_t in_epaddr; | ||||
std::uint8_t out_epaddr; | ||||
daschuer marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
std::uint8_t interface_number; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where do you get that from? its uint8 in the relevant struct. https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/libusb.h#L749 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. strange, everywhere else in libusb.h it is an int. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You point to bInterfaceNumber which is a byte. Everywhere else it is used as int because of -1 for invalid. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have the option to revert the change here and carry on, there was no user visible issue with the old types, was it? If we decide for a new type here we may adopt Where is the term "tweak" coming from and why is it referenced as value in the structure and not as tweak? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interface number along with 0 have been introduced in a bulk action here: What does 0 mean? Why are all 0? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not really. I can revert if that is seriously a condition to merge... But fixing user-visible issues is not really the point of a refactor, is it?
Its the default. 0 on most devices, but not all (thus the table).
Because those are the nonstandard values we actually want to tweak. I can also call it "quirk", which is the terminology the linux kernel uses for this sort of thing.
Because the only device that has a non-zero interface has not been added yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's just numbering of the interfaces. We currently just support bulk devices from the manufacturer Hercules. And Hercules always use the first interface for bulk and the second for audio. This order is completely up to the manufacturer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least untill all numbers are known we need here int(-1) for invalid. see my other comment.
Swiftb0y marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
}; | ||||
|
||||
typedef struct bulk_supported { | ||||
unsigned short vendor_id; | ||||
unsigned short product_id; | ||||
unsigned char in_epaddr; | ||||
unsigned char out_epaddr; | ||||
unsigned int interface_number; | ||||
} bulk_supported_t; | ||||
struct bulk_support_lookup { | ||||
bulk_device_id key; | ||||
bulk_device_endpoints endpoints; | ||||
}; | ||||
|
||||
static bulk_supported_t bulk_supported[] = { | ||||
{0x06f8, 0xb105, 0x82, 0x03, 0}, // Hercules MP3e2 | ||||
{0x06f8, 0xb107, 0x83, 0x03, 0}, // Hercules Mk4 | ||||
{0x06f8, 0xb100, 0x86, 0x06, 0}, // Hercules Mk2 | ||||
{0x06f8, 0xb120, 0x82, 0x03, 0}, // Hercules MP3 LE / Glow | ||||
{0, 0, 0, 0, 0}}; | ||||
constexpr static bulk_support_lookup bulk_supported[] = { | ||||
{{0x06f8, 0xb105}, {0x82, 0x03, 0}}, // Hercules MP3e2 | ||||
{{0x06f8, 0xb107}, {0x83, 0x03, 0}}, // Hercules Mk4 | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interestingly the MK4 is reported here at: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no idea. @JoergAtGithub may know. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Workaround: Add a TODO, and use -1 as interfac number. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is not the same USB endpoint, because only the address Endpoints and Interfaces are fields on different USB protocol layers:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am still confused, because we don't use endpoint number in our code only the address. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is still puzzling form me, because we do not consider the Interface number with Linux at all but it seams to work with the enpoint adress only, even without an Endpoint number. libusb_claim_interface() is written in a way that we may claim multiple interfaces with one handel. mixxx/src/controllers/bulk/bulkcontroller.cpp Line 199 in 4df2508
libusb_bulk_transfer Here we use only the enpoint adress The underlying questions is what is the interface number for -1 and will it work like before? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I double checked this, and I found out, that the Endpoint-Number is part of the Endpoint-Address:
See https://beyondlogic.org/usbnutshell/usb5.shtml for reference |
||||
{{0x06f8, 0xb100}, {0x86, 0x06, 0}}, // Hercules Mk2 | ||||
{{0x06f8, 0xb120}, {0x82, 0x03, 0}}, // Hercules MP3 LE / Glow | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to this post: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interface number is also there for Linux and the API documentation does not exclude the usage there:
This is skipped in our implementation for Linux. However I can read here https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/core.c#L2072C4-L2072C51: But I think it is intended to be always called and then checked for Conclusion: something is broken here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, but I'm doubtful there is much to fix if we don't know what and have no hardware to test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @acolombier Do you have a device to test it? My idea is that we need to remove all conditional code and just let libusb do the OS abstarction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be ideal, yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why is the situation on windows special? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know, it looks like it was working on Linux without specifying the interface when it has been introduced. I hope @acolombier can give some insight as he has enabled bulk support for windows here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have the S4 Mk3 to test, which has non-zero interface. I have add the interface number, which was missing in the previous implementation, so I needed a none value to exclude the other device on which I couldn't confirm the value.
Without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, note that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,35 @@ | ||
#pragma once | ||
|
||
typedef struct hid_denylist { | ||
// TODO: unify this with the invalid interfacenumber from the bulkenumerator | ||
constexpr static int kInvalidInterfaceNumber = -1; | ||
constexpr static unsigned short kAnyValue = 0x0; | ||
|
||
struct hid_denylist_t { | ||
unsigned short vendor_id; | ||
unsigned short product_id; | ||
unsigned short usage_page; | ||
unsigned short usage; | ||
int interface_number; | ||
daschuer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} hid_denylist_t; | ||
int interface_number = kInvalidInterfaceNumber; | ||
}; | ||
|
||
/// USB HID device that should not be recognized as controllers | ||
constexpr hid_denylist_t hid_denylisted[] = { | ||
{0x1157, 0x300, 0x1, 0x2, -1}, // EKS Otus mouse pad (OS/X,windows) | ||
{0x1157, 0x300, 0x0, 0x0, 0x3}, // EKS Otus mouse pad (linux) | ||
{0x04f3, 0x2d26, 0x0, 0x0, -1}, // ELAN2D26:00 Touch screen | ||
{0x046d, 0xc539, 0x0, 0x0, -1}, // Logitech G Pro Wireless | ||
constexpr static hid_denylist_t hid_denylisted[] = { | ||
{0x1157, 0x300, 0x1, 0x2}, // EKS Otus mouse pad (OS/X,windows) | ||
{0x1157, 0x300, kAnyValue, kAnyValue, 0x3}, // EKS Otus mouse pad (linux) | ||
{0x04f3, 0x2d26, kAnyValue, kAnyValue}, // ELAN2D26:00 Touch screen | ||
{0x046d, 0xc539, kAnyValue, kAnyValue}, // Logitech G Pro Wireless | ||
// The following rules have been created using the official USB HID page | ||
// spec as specified at https://usb.org/sites/default/files/hut1_4.pdf | ||
{0x0, 0x0, 0x0D, 0x04, -1}, // Touch Screen | ||
{0x0, 0x0, 0x0D, 0x22, -1}, // Finger | ||
{ | ||
kAnyValue, | ||
kAnyValue, | ||
0x0D, | ||
0x04, | ||
}, // Touch Screen | ||
{ | ||
kAnyValue, | ||
kAnyValue, | ||
0x0D, | ||
0x22, | ||
}, // Finger | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid this confusing style with an assignment in an if condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this confusing? Its just a new syntax which makes sense here since I don't need
ret
outside the if-bodyhttps://en.cppreference.com/w/cpp/language/if#if_statements_with_initializer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it break the flow when reading the code. You expect an if condition like everywhere else but you read the assignment, which is every where else in the line before. This reduces IMHO maintainability and lead to misunderstandings. The benefit of the tight scope is negligible here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree actually because if the variable does not get its own scope the variables storing the return values either need different names or the one variable needs to be reassigned. The different names is confusing because that leads me to believe that we're actually interested in this value while reassigning looks weird because the added mutability makes the code hard to reason about.
Well you only expect the condition there because you're not used to there being something else but this is a well designed and documented feature so its reasonable to expect it to be usable. The same applies to other "new" features such as lambdas or trailing return types.
Let me reiterate that this very much distinct from the relying on return value of
operator=
(eg.if (int var = f()
) because that is mixing an init-statement with a condition while the latter one is clearly defined.This is just a case of "Was der Bauer nicht kennt, frisst er nicht".
I won't insist on using this style, but resisting to embrace new and encouraged features and styles is a common pattern in code review. This is neither rewarding nor productive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have considered this a bit and it turns out that this is a general code style question, we should discuss on Zulip.
We have a plenty of places where we may consider to tighten the scope of a local variable, because we can. On the other hand we don't care about them, because it will just introduce boiler plate and nesting, interrupting the flow when reading the code. This is an issue when you only skim over the code, and especially when the if condition is broken over two lines of code.
I have no interest to ban this new statement from the Mixxx code entirely, but let's use it deliberately, backed up by a rule in our style guide.
This is here an edge case because we have two return values that will end up in the same variable of temporary nature otherwise. For now this is a new style, that does not fit to the rest of Mixxx. I am open to decide in any direction, but let's do it consistently.
I will not block the PR on this point, but prefer reusing
ret
here without a scope.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://mixxx.zulipchat.com/#narrow/stream/109171-development/topic/C.2B.2B17.20if.20with.20init-statement