Bug 1699299 [wpt PR 28122] - usb: Support devices without interfaces in macOS backend, a=testonly

Automatic update from web-platform-tests
usb: Support devices without interfaces in macOS backend

This change fixes support for USB devices that do not have any
interfaces by using the DeviceRequestAsyncTO method instead of
ControlRequestAsyncTO when submitting control transfers as the
latter assumes an IOUsbInterfaceInterface struct can be found.

A new manual tests which exercises the controlTransferIn() method is
added to exercise this codepath.

Bug: 1096743
Change-Id: I4601c7526ad08fe9f3be8cb7983e00c54266ee12
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2770479
Auto-Submit: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Chris Mumford <cmumford@google.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#864775}

--

wpt-commits: 09da3f87a972bda79a1f4a542fc891a791735e6f
wpt-pr: 28122
This commit is contained in:
Reilly Grant 2021-03-19 22:12:42 +00:00 коммит произвёл moz-wptsync-bot
Родитель 64d6da2f00
Коммит 4d880b8740
1 изменённых файлов: 348 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,348 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/manual.js"></script>
</head>
<body>
<p>
These tests require a USB device to be connected.
</p>
<script>
const kGetDescriptorRequest = 0x06;
const kDeviceDescriptorType = 0x01;
const kDeviceDescriptorLength = 18;
const kConfigurationDescriptorType = 0x02;
const kConfigurationDescriptorLength = 9;
const kStringDescriptorType = 0x03;
const kStringDescriptorMaxLength = 0xFF;
const kInterfaceDescriptorType = 0x04;
const kInterfaceDescriptorLength = 9;
const kEndpointDescriptorType = 0x05;
const kEndpointDescriptorLength = 7;
let device = null;
let pending_subtests = 0;
const string_tests = new Set();
const string_languages = [];
async function subtest_complete() {
if (--pending_subtests == 0) {
await device.close();
}
}
function manual_usb_subtest(func, name, properties) {
pending_subtests++;
promise_test(async (test) => {
test.add_cleanup(subtest_complete);
await func(test);
}, name, properties);
}
function read_string(index, expected) {
// A device may use the same string in multiple places. Don't bother
// repeating the test.
if (string_tests.has(index)) {
return;
}
string_tests.add(index);
const string_values = new Set();
const decoder = new TextDecoder('utf-16le');
for (const language of string_languages) {
const language_string = language.toString(16).padStart(4, '0');
manual_usb_subtest(async (t) => {
if (expected != undefined) {
t.add_cleanup(() => {
if (string_values.size == string_languages.length) {
assert_true(string_values.has(expected));
}
});
}
const result = await device.controlTransferIn({
requestType: 'standard',
recipient: 'device',
request: kGetDescriptorRequest,
value: kStringDescriptorType << 8 | index,
index: language
}, kStringDescriptorMaxLength);
assert_equals(result.status, 'ok', 'transfer status');
const length = result.data.getUint8(0);
assert_greater_than_equal(length, 2, 'descriptor length');
assert_equals(result.data.byteLength, length, 'transfer length');
assert_equals(result.data.getUint8(1), kStringDescriptorType,
'descriptor type');
const string_buffer = new Uint8Array(
result.data.buffer, result.data.byteOffset + 2, length - 2);
string_values.add(decoder.decode(string_buffer));
},
`Read string descriptor ${index} in language 0x${language_string}`);
}
}
function check_interface_descriptor(configuration, data) {
assert_greater_than_equal(
data.getUint8(0), kInterfaceDescriptorLength, 'descriptor length');
const interface_number = data.getUint8(2);
const iface = configuration.interfaces.find((iface) => {
return iface.interfaceNumber == interface_number;
});
assert_not_equals(
iface, undefined, `unknown interface ${interface_number}`);
const alternate_setting = data.getUint8(3);
const alternate = iface.alternates.find((alternate) => {
return alternate.alternateSetting == alternate_setting;
});
assert_not_equals(
alternate, undefined, `unknown alternate ${alternate_setting}`);
assert_equals(data.getUint8(4), alternate.endpoints.length,
'number of endpoints');
assert_equals(
data.getUint8(5), alternate.interfaceClass, 'interface class');
assert_equals(data.getUint8(6), alternate.interfaceSubclass,
'interface subclass');
assert_equals(data.getUint8(7), alternate.interfaceProtocol,
'interface protocol');
const interface_string = data.getUint8(8);
if (interface_string != 0) {
// TODO(crbug.com/727819): Check that the string descriptor matches
// iface.interfaceName.
read_string(interface_string);
}
return alternate;
}
function check_endpoint_descriptor(alternate, data) {
assert_greater_than_equal(
data.getUint8(0), kEndpointDescriptorLength, 'descriptor length');
const endpoint_address = data.getUint8(2);
const direction = endpoint_address & 0x80 ? 'in' : 'out';
const endpoint_number = endpoint_address & 0x0f;
const endpoint = alternate.endpoints.find((endpoint) => {
return endpoint.direction == direction &&
endpoint.endpointNumber == endpoint_number;
});
assert_not_equals(
endpoint, undefined, `unknown endpoint ${endpoint_number}`);
const attributes = data.getUint8(3);
switch (attributes & 0x03) {
case 0:
assert_equals(endpoint.type, 'control', 'endpoint type');
break;
case 1:
assert_equals(endpoint.type, 'isochronous', 'endpoint type');
break;
case 2:
assert_equals(endpoint.type, 'bulk', 'endpoint type');
break;
case 3:
assert_equals(endpoint.type, 'interrupt', 'endpoint type');
break;
}
assert_equals(data.getUint16(4, /*littleEndian=*/true),
endpoint.packetSize, 'packet size');
}
function read_config_descriptor(config_value) {
manual_usb_subtest(async (t) => {
const configuration = device.configurations.find((config) => {
return config.configurationValue == config_value;
});
assert_not_equals(configuration, undefined);
let result = await device.controlTransferIn({
requestType: 'standard',
recipient: 'device',
request: kGetDescriptorRequest,
value: kConfigurationDescriptorType << 8 | (config_value - 1),
index: 0
}, kConfigurationDescriptorLength);
assert_equals(result.status, 'ok', 'transfer status');
let length = result.data.getUint8(0);
assert_greater_than_equal(
length, kConfigurationDescriptorLength, 'descriptor length');
assert_equals(result.data.byteLength, length, 'transfer length');
const total_length = result.data.getUint16(2, /*littleEndian=*/true);
result = await device.controlTransferIn({
requestType: 'standard',
recipient: 'device',
request: kGetDescriptorRequest,
value: kConfigurationDescriptorType << 8 | (config_value - 1),
index: 0
}, total_length);
assert_equals(result.status, 'ok', 'transfer status');
assert_equals(
result.data.byteLength, total_length, 'transfer length');
assert_equals(result.data.getUint8(0), length, 'descriptor length');
assert_equals(result.data.getUint8(1), kConfigurationDescriptorType,
'descriptor type');
assert_equals(result.data.getUint16(2, /*littleEndian=*/true),
total_length, 'total length');
assert_equals(
result.data.getUint8(4), configuration.interfaces.length,
'number of interfaces');
assert_equals(
result.data.getUint8(5), config_value, 'configuration value');
const configuration_string = result.data.getUint8(6);
if (configuration_string != 0) {
// TODO(crbug.com/727819): Check that the string descriptor matches
// configuration.configurationName.
read_string(configuration_string);
}
let offset = length;
let alternate = undefined;
while (offset < total_length) {
length = result.data.getUint8(offset);
assert_less_than_equal(offset + length, total_length);
const view = new DataView(
result.data.buffer, result.data.byteOffset + offset, length);
switch (view.getUint8(1)) {
case kConfigurationDescriptorType:
assert_unreached('cannot contain multiple config descriptors');
break;
case kInterfaceDescriptorType:
alternate = check_interface_descriptor(configuration, view);
break;
case kEndpointDescriptorType:
assert_not_equals(alternate, undefined,
'endpoint not defined after interface');
check_endpoint_descriptor(alternate, view);
break;
}
offset += length;
}
}, `Read config descriptor ${config_value}`);
}
function read_string_descriptor_languages(device_descriptor) {
manual_usb_subtest(async (t) => {
const result = await device.controlTransferIn({
requestType: 'standard',
recipient: 'device',
request: kGetDescriptorRequest,
value: kStringDescriptorType << 8,
index: 0
}, kStringDescriptorMaxLength);
assert_equals(result.status, 'ok', 'transfer status');
assert_equals(result.data.getUint8(1), kStringDescriptorType,
'descriptor type');
const length = result.data.getUint8(0);
assert_greater_than_equal(length, 2, 'descriptor length')
assert_greater_than_equal(
result.data.byteLength, length, 'transfer length');
for (let index = 2; index < length; index += 2) {
string_languages.push(
result.data.getUint16(index, /*littleEndian=*/true));
}
const manufacturer_string = device_descriptor.getUint8(14);
if (manufacturer_string != 0) {
assert_not_equals(device.manufacturerName, undefined);
read_string(manufacturer_string, device.manufacturerName);
}
const product_string = device_descriptor.getUint8(15);
if (product_string != 0) {
assert_not_equals(device.productName, undefined);
read_string(product_string, device.productName);
}
const serial_number_string = device_descriptor.getUint8(16);
if (serial_number_string != 0) {
assert_not_equals(device.serialNumber, undefined);
read_string(serial_number_string, device.serialNumber);
}
const num_configurations = device_descriptor.getUint8(17);
for (let config_value = 1; config_value <= num_configurations;
++config_value) {
read_config_descriptor(config_value);
}
}, `Read supported languages`);
}
promise_test(async (t) => {
device = await getDeviceForManualTest();
await device.open();
const result = await device.controlTransferIn({
requestType: 'standard',
recipient: 'device',
request: kGetDescriptorRequest,
value: kDeviceDescriptorType << 8,
index: 0,
}, kDeviceDescriptorLength);
assert_equals(result.status, 'ok', 'transfer status');
assert_equals(
result.data.byteLength, kDeviceDescriptorLength, 'transfer length');
assert_greater_than_equal(
result.data.getUint8(0),
kDeviceDescriptorLength, 'descriptor length');
assert_equals(result.data.getUint8(1), kDeviceDescriptorType,
'descriptor type');
const bcd_usb = result.data.getUint16(2, /*littleEndian=*/true);
assert_equals(
bcd_usb >> 8, device.usbVersionMajor, 'USB version major');
assert_equals(
(bcd_usb & 0xf0) >> 4, device.usbVersionMinor, 'USB version minor');
assert_equals(
bcd_usb & 0xf, device.usbVersionSubminor, 'USV version subminor');
assert_equals(
result.data.getUint8(4), device.deviceClass, 'device class');
assert_equals(
result.data.getUint8(5), device.deviceSubclass, 'device subclass');
assert_equals(
result.data.getUint8(6), device.deviceProtocol, 'device protocol');
assert_equals(result.data.getUint16(8, /*littleEndian=*/true),
device.vendorId, 'vendor id');
assert_equals(result.data.getUint16(10, /*littleEndian=*/true),
device.productId, 'product id');
const bcd_device = result.data.getUint16(12, /*littleEndian=*/true);
assert_equals(
bcd_device >> 8, device.deviceVersionMajor, 'device version major');
assert_equals((bcd_device & 0xf0) >> 4, device.deviceVersionMinor,
'device version minor');
assert_equals(bcd_device & 0xf, device.deviceVersionSubminor,
'device version subminor');
assert_equals(result.data.getUint8(17), device.configurations.length,
'number of configurations');
read_string_descriptor_languages(result.data);
if (pending_subtests == 0) {
await device.close();
}
}, 'Read device descriptor');
</script>
</body>
</html>