from typing import List, Optional
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.defs import BIT
from hwt.hdl.types.struct import HStruct
from hwt.synthesizer.rtlLevel.constants import NOT_SPECIFIED
from hwtLib.peripheral.usb.constants import USB_VER
from hwtLib.peripheral.usb.device_request import USB_REQUEST_TYPE_RECIPIENT, \
USB_REQUEST_TYPE_TYPE, USB_REQUEST_TYPE_DIRECTION, USB_REQUEST, make_usb_device_request
from hwtLib.types.ctypes import uint8_t, uint16_t
"""
This module contains a definition of standard USB descriptor types
and constants.
In addition there are class specific/functional descriptors.
A class-specific descriptor exists only at the Interface level.
Each class-specific descriptor is defined as a concatenation ofall
of the functional descriptors for the Interface. The first functional descriptor
returned by the device for the interfaceshall be a header functional descriptor.
"""
# https://beyondlogic.org/usbnutshell/usb6.shtml
# https://www.engineersgarage.com/featured-contributions/usb-descriptors-and-their-types-part-3-6/
[docs]class USB_DEVICE_CLASS():
"""
https://www.usb.org/defined-class-codes
"""
UNSPECIFIED = 0x00
AUDIO = 0x01
CDC_CONTROL = 0x02 # Communications and CDC Control
HID = 0x03 # Human Interface Device
USB_PID = 0x05 # Physical Interface Device
IMAGE = 0x06
PRINTER = 0x07
MASS_STORAGE = 0x08
USB_HUB = 0x09
CDC_DATA = 0x0A
SMART_CARD = 0x0B
CONTENT_SECURITY = 0x0D
VIDEO = 0x0E
PERSONAL_HEALTHCARE = 0x0F
AUDIO_VIDEO_DEVICE = 0x10
BILBOARD_DEVICE = 0x11
USB_TYPE_C_BRIDGE = 0x12
DIAGNOSTIC_DEVICE = 0xDC
WIRELESS_CONTROLLER = 0xE0
MISCELLANEOUS = 0xEF
APPLICATION_SPECIFIC = 0xFE
VENDOR_SPECIFIC = 0xFF
[docs]class USB_DESCRIPTOR_TYPE():
DEVICE = 1
CONFIGURATION = 2
STRING = 3
INTERFACE = 4
ENDPOINT = 5
DEVICE_QUALIFIER = 6
OTHER_SPEED_CONFIGURATION = 7
INTERFACE_POWER = 8
HID = 0x21
REPORT = 0x22
FUNCTIONAL = 0x24
USB_2_0_PACKET_SIEZES = (8, 16, 32, 64, 128, 256, 512)
# https://www.beyondlogic.org/usbnutshell/usb5.shtml#EndpointDescriptors
usb_descriptor_header_t = HStruct(
(uint8_t, "bLength"), # (in bytes, including this header)
(uint8_t, "bDescriptorType"),
name="usb_descriptor_header_t",
)
usb_descriptor_device_body_t = HStruct(
# The bcdUSB field reports the highest version of USB the device supports.
# Use :func:`~bdc_encode_version`
(uint16_t, "bcdUSB"),
# Class Code (Assigned by USB Org)
# If equal to Zero, each interface specifies it’s own class code
# If equal to 0xFF, the class code is vendor specified.
# Otherwise field is valid Class Code.
(uint8_t, "bDeviceClass"),
# Subclass Code (Assigned by USB Org)
(uint8_t, "bDeviceSubClass"),
# Protocol Code (Assigned by USB Org)
(uint8_t, "bDeviceProtocol"),
# Maximum Packet Size for Zero Endpoint. Valid Sizes are 8, 16, 32, 64
(uint8_t, "bMaxPacketSize"),
# Vendor ID (Assigned by USB Org)
(uint16_t, "idVendor"),
# Product ID (Assigned by Manufacturer)
(uint16_t, "idProduct"),
# Device Release Number, you may use :func:`~bdc_encode_version`
(uint16_t, "bcdDevice"),
# Index of Manufacturer String Descriptor
(uint8_t, "iManufacturer"),
# Index of Product String Descriptor
(uint8_t, "iProduct"),
# Index of Serial Number String Descriptor
(uint8_t, "iSerialNumber"),
# Number of Possible Configurations
(uint8_t, "bNumConfigurations"),
name="usb_descriptor_device_body_t",
)
usb_descriptor_device_t = HStruct(
(usb_descriptor_header_t, "header"),
(usb_descriptor_device_body_t, "body"),
name="usb_descriptor_device_t"
)
[docs]def make_usb_descriptor_device(
bDeviceClass: USB_DEVICE_CLASS, bDeviceSubClass=0x00, bDeviceProtocol=0xff,
usbVer: USB_VER=USB_VER.USB2_0, bMaxPacketSize=64,
idVendor=0x1234, idProduct=0x5678, # Micro Science Co., Ltd. Disk 2.0
bcdDevice="0.1",
iManufacturer=1,
iProduct=2,
iSerialNumber=0,
bNumConfigurations=1):
assert bMaxPacketSize in USB_2_0_PACKET_SIEZES, bMaxPacketSize
t = usb_descriptor_device_t
return t.from_py({
"header": {
"bLength": t.bit_length() // 8,
"bDescriptorType": USB_DESCRIPTOR_TYPE.DEVICE
},
"body": {
"bcdUSB": USB_VER.to_uint16_t(usbVer),
"bDeviceClass": bDeviceClass,
"bDeviceSubClass": bDeviceSubClass,
"bDeviceProtocol": bDeviceProtocol,
"bMaxPacketSize": bMaxPacketSize,
"idVendor": idVendor,
"idProduct": idProduct,
"bcdDevice": USB_VER.to_uint16_t(bcdDevice),
"iManufacturer": iManufacturer,
"iProduct": iProduct,
"iSerialNumber": iSerialNumber,
"bNumConfigurations": bNumConfigurations,
},
})
# A high-speed capable device that has different device information
# for full-speed and high-speed must specify this descriptor.
# It contains the same informatio as device descriptor but it is specific to high-speed.
# :note: for description of fields check :var:`~.usb_descriptor_device_body_t`
usb_descriptor_device_qualifier_body_t = HStruct(
(uint16_t, "bcdUSB"),
(uint8_t, "bDeviceClass"),
(uint8_t, "bDeviceSubClass"),
(uint8_t, "bDeviceProtocol"),
(uint8_t, "bMaxPacketSize"),
(uint8_t, "bNumConfigurations"),
(uint8_t, "reserved0"), # must be set to 0
name="usb_descriptor_device_qualifier_body_t",
)
usb_descriptor_device_qualifier_t = HStruct(
(usb_descriptor_header_t, "header"),
(usb_descriptor_device_qualifier_body_t, "body"),
name="usb_descriptor_device_qualifier_t",
)
[docs]def make_usb_descriptor_device_qualifier(
bDeviceClass: USB_DEVICE_CLASS, bDeviceSubClass=0x00, bDeviceProtocol=0xff,
usbVer: USB_VER=USB_VER.USB2_0, bMaxPacketSize=64,
bNumConfigurations=1):
assert bMaxPacketSize in USB_2_0_PACKET_SIEZES, bMaxPacketSize
t = usb_descriptor_device_qualifier_t
return t.from_py({
"header": {
"bLength": t.bit_length() // 8,
"bDescriptorType": USB_DESCRIPTOR_TYPE.DEVICE_QUALIFIER
},
"body": {
"bcdUSB": USB_VER.to_uint16_t(usbVer),
"bDeviceClass": bDeviceClass,
"bDeviceSubClass": bDeviceSubClass,
"bDeviceProtocol": bDeviceProtocol,
"bMaxPacketSize": bMaxPacketSize,
"bNumConfigurations": bNumConfigurations,
"reserved0": 0,
},
})
usb_configuration_body_bmAttributes_t = HStruct(
(Bits(5), "reserved0"), # has to be set to 0
(Bits(1), "remoteWakeup"),
(Bits(1), "selfPowered"),
(Bits(1), "reserved1"), # has to be set to 1
name="usb_configuration_body_bmAttributes_t"
)
usb_descriptor_configuration_body_t = HStruct(
# Total length in bytes of data returned (sum of this and all functional and endpoint descriptors)
# :note: All interface, endpoint and functional descriptors are
# downloaded as a part of configuration descriptor.
(uint16_t, "wTotalLength"),
# Number of Interfaces of interfaces present for this configuration.
(uint8_t, "bNumInterfaces"),
# Value to use as an argument to select this configuration
(uint8_t, "bConfigurationValue"),
# Index of String Descriptor describing this configuration
(uint8_t, "iConfiguration"),
(usb_configuration_body_bmAttributes_t, "bmAttributes"),
# Maximum Power Consumption in 2mA units
(uint8_t, "bMaxPower"),
name="usb_descriptor_configuration_body_t",
)
usb_descriptor_configuration_t = HStruct(
(usb_descriptor_header_t, "header"),
(usb_descriptor_configuration_body_t, "body"),
name="usb_descriptor_configuration_t"
)
[docs]def make_usb_descriptor_configuration(wTotalLength:int,
bNumInterfaces:int,
bConfigurationValue:int,
iConfiguration:int,
remoteWakeup:int=0,
selfPowered:int=0,
bMaxPower:int=usb_format_bMaxPower(500)):
t = usb_descriptor_configuration_t
return t.from_py({
"header": {
"bLength": t.bit_length() // 8,
"bDescriptorType": USB_DESCRIPTOR_TYPE.CONFIGURATION
},
"body": {
"wTotalLength": wTotalLength,
"bNumInterfaces": bNumInterfaces,
"bConfigurationValue": bConfigurationValue,
"iConfiguration": iConfiguration,
"bmAttributes": {
"reserved0": 0,
"remoteWakeup": remoteWakeup,
"selfPowered": selfPowered,
"reserved1": 1,
},
"bMaxPower": bMaxPower,
}
})
# same as usb_descriptor_configuration_t just different bDescriptor type
usb_descriptor_other_speed_configuration_t = HStruct(
*usb_descriptor_configuration_t.fields,
name="usb_descriptor_other_speed_configuration_t",
)
usb_descriptor_interface_body_t = HStruct(
# Number of Interface
(uint8_t, "bInterfaceNumber"),
# Value used to select alternative setting
(uint8_t, "bAlternateSetting"),
# Number of Endpoints used for this interface
(uint8_t, "bNumEndpoints"),
# Class Code (Assigned by USB Org)
(uint8_t, "bInterfaceClass"),
# Subclass Code (Assigned by USB Org)
(uint8_t, "bInterfaceSubClass"),
# Protocol Code (Assigned by USB Org)
(uint8_t, "bInterfaceProtocol"),
# Index of String Descriptor Describing this interface
(uint8_t, "iInterface"),
name="usb_descriptor_interface_body_t",
)
usb_descriptor_interface_t = HStruct(
(usb_descriptor_header_t, "header"),
(usb_descriptor_interface_body_t, "body"),
name="usb_descriptor_interface_t",
)
[docs]def make_usb_descriptor_interface(
bInterfaceNumber:int, bAlternateSetting:int,
bNumEndpoints: int, bInterfaceClass:USB_DEVICE_CLASS,
bInterfaceSubClass:int, bInterfaceProtocol:int, iInterface:int):
t = usb_descriptor_interface_t
return t.from_py({
"header": {
"bLength": t.bit_length() // 8,
"bDescriptorType": USB_DESCRIPTOR_TYPE.INTERFACE
},
"body": {
"bInterfaceNumber":bInterfaceNumber,
"bAlternateSetting":bAlternateSetting,
"bNumEndpoints":bNumEndpoints,
"bInterfaceClass":bInterfaceClass,
"bInterfaceSubClass":bInterfaceSubClass,
"bInterfaceProtocol":bInterfaceProtocol,
"iInterface": iInterface,
}
})
[docs]class USB_ENDPOINT_DIR:
OUT = 0
IN = 1
[docs]class USB_ENDPOINT_ATTRIBUTES_TRANSFER_TYPE:
CONTROL = 0
ISOCHRONOUS = 1
BULK = 2
INTERRUPT = 3
[docs]class USB_ENDPOINT_ATTRIBUTES_SYNCHRONISATION_TYPE:
"""
Note only apply to isochronous endpoints.
"""
NONE = 0
ASYNCHRONOUS = 1
ADAPTIVE = 2
SYNCHRONOUS = 3
[docs]class USB_ENDPOINT_ATTRIBUTES_USAGE_TYPE:
DATA = 0
FEEDBACK = 1
IMPLICIT_FEEDBACK_DATA = 2
RESERVED = 3
usb_configuration_body_bmAttributes_t = HStruct(
(Bits(2), "transferType"), # :note: :class:`~.USB_ENDPOINT_ATTRIBUTES_TRANSFER_TYPE`
(Bits(2), "synchronisationType"), # :note: :class:`~.USB_ENDPOINT_ATTRIBUTES_SYNCHRONISATION_TYPE`
(Bits(2), "usageType"), # :note: :class:`~.USB_ENDPOINT_ATTRIBUTES_USAGE_TYPE`
(Bits(2), "reserved0"), # has to be set to 0
name="usb_configuration_body_bmAttributes_t",
)
usb_descriptor_endpoint_body_t = HStruct(
# The address of this endpoint within the device.
(Bits(7), "bEndpointAddress"),
(BIT, "bEndpointAddressDir"), # :note: :class:`~.USB_ENDPOINT_DIR`
(usb_configuration_body_bmAttributes_t, "bmAttributes"),
(uint16_t, "wMaxPacketSize"),
# Expressed in frames (ms) for low/full speed or microframes (125us) for high speed.
(uint8_t, "bInterval"),
name="usb_descriptor_endpoint_body_t",
)
usb_descriptor_endpoint_t = HStruct(
(usb_descriptor_header_t, "header"),
(usb_descriptor_endpoint_body_t, "body"),
name="usb_descriptor_endpoint_t",
)
[docs]def make_usb_descriptor_endpoint(
bEndpointAddressDir:USB_ENDPOINT_DIR,
bEndpointAddress:int,
attr_transferType:USB_ENDPOINT_ATTRIBUTES_TRANSFER_TYPE,
attr_synchronisationType: USB_ENDPOINT_ATTRIBUTES_SYNCHRONISATION_TYPE,
attr_usageType:USB_ENDPOINT_ATTRIBUTES_USAGE_TYPE,
wMaxPacketSize: int,
bInterval: int,
):
assert bEndpointAddress > 0, bEndpointAddress
t = usb_descriptor_endpoint_t
return t.from_py({
"header": {
"bLength": t.bit_length() // 8,
"bDescriptorType": USB_DESCRIPTOR_TYPE.ENDPOINT,
},
"body": {
"bEndpointAddress": bEndpointAddress,
"bEndpointAddressDir":bEndpointAddressDir,
"bmAttributes": {
"transferType": attr_transferType,
"synchronisationType": attr_synchronisationType,
"usageType": attr_usageType,
"reserved0": 0,
},
"wMaxPacketSize": wMaxPacketSize,
"bInterval": bInterval,
}
})
[docs]def usb_define_descriptor_string0(lang_cnt:int):
return HStruct(
(usb_descriptor_header_t, "header"),
(uint16_t[lang_cnt], "body"),
)
[docs]def make_usb_descriptor_string0(langIds: List[int]):
t = usb_define_descriptor_string0(len(langIds))
return t.from_py({
"header": {
"bLength": usb_descriptor_header_t.bit_length() // 8 + 2 * len(langIds),
"bDescriptorType": USB_DESCRIPTOR_TYPE.STRING,
},
"body": langIds
})
LANG_ID_EN_US = 0x0409
default_usb_descriptor_string0 = make_usb_descriptor_string0([LANG_ID_EN_US])
[docs]def usb_define_descriptor_string(utf16_char_cnt):
return HStruct(
(usb_descriptor_header_t, "header"),
(uint8_t[utf16_char_cnt * 2], "body"),
)
[docs]def make_usb_descriptor_string(s: str):
as_utf_16 = s.encode("utf-16")
t = usb_define_descriptor_string(len(as_utf_16) // 2)
return t.from_py({
"header": {
"bLength": usb_descriptor_header_t.bit_length() // 8 + len(as_utf_16),
"bDescriptorType": USB_DESCRIPTOR_TYPE.STRING,
},
"body": [b for b in as_utf_16]
})
[docs]def make_usb_device_request_get_descr(descr_t, i: int, wIndex=0, wLength: Optional[int]=NOT_SPECIFIED):
if descr_t is usb_descriptor_device_t:
des_t_id = USB_DESCRIPTOR_TYPE.DEVICE
elif descr_t is usb_descriptor_configuration_t:
des_t_id = USB_DESCRIPTOR_TYPE.CONFIGURATION
elif descr_t is str:
des_t_id = USB_DESCRIPTOR_TYPE.STRING
elif descr_t is usb_descriptor_interface_t:
des_t_id = USB_DESCRIPTOR_TYPE.INTERFACE
elif descr_t is usb_descriptor_endpoint_t:
des_t_id = USB_DESCRIPTOR_TYPE.ENDPOINT
elif descr_t is usb_descriptor_device_qualifier_t:
des_t_id = USB_DESCRIPTOR_TYPE.DEVICE_QUALIFIER
elif descr_t is usb_descriptor_other_speed_configuration_t:
des_t_id = USB_DESCRIPTOR_TYPE.OTHER_SPEED_CONFIGURATION
else:
# Interface_power Descriptor was proposed by Microsoft 1998 but hasn’t been implemented.
raise ValueError(descr_t)
if wLength is NOT_SPECIFIED:
if descr_t is str:
wLength = 255
else:
wLength = descr_t.bit_length() // 8
return make_usb_device_request(
bmRequestType_recipient=USB_REQUEST_TYPE_RECIPIENT.DEVICE,
bmRequestType_type=USB_REQUEST_TYPE_TYPE.STANDARD,
bmRequestType_data_transfer_direction=USB_REQUEST_TYPE_DIRECTION.DEV_TO_HOST,
bRequest=USB_REQUEST.GET_DESCRIPTOR,
wValue=(des_t_id << 8) | i,
wIndex=wIndex,
wLength=wLength)