%% @doc Erlang interface for interacting with USB devices. -module(usb). -export([ get_device_list/0 ]). -export([ get_bus_number/1, get_port_number/1, get_port_numbers/1, get_device_address/1, get_device_speed/1, get_device_descriptor/1, get_config_descriptor/2 ]). -export([ open_device/1, close_device/1, claim_interface/2, release_interface/2, clear_halt/2, set_configuration/2, read_bulk/4, write_bulk/4, read_interrupt/4, write_interrupt/4, read_control/7, write_control/7, attach_kernel_driver/2, detach_kernel_driver/2 ]). -export([ monitor_hotplug/0, monitor_hotplug/1, demonitor_hotplug/1 ]). -export_type([ device/0, device_handle/0, hotplug_monitor/0 ]). -on_load(init/0). % % Types % -opaque device() :: reference(). %% Reference to a USB device attached to the host system. -type device_speed() :: unknown | low | full | high | super | super_plus. %% Indicates the speed at which the device is operating. %% %%
%%
`unknown'
%%
The OS doesn't report or know the device speed.
%%
`low'
%%
The device is operating at low speed (1.5MBit/s).
%%
`full'
%%
The device is operating at full speed (12MBit/s).
%%
`high'
%%
The device is operating at high speed (480MBit/s).
%%
`super'
%%
The device is operating at super speed (5000MBit/s).
%%
`super_plus'
%%
The device is operating at super speed plus (10000MBit/s).
%%
-type device_descriptor() :: #{ usb_version := 0..16#ffff, class_code := byte(), sub_class_code := byte(), protocol_code := byte(), max_packet_size0 := byte(), vendor_id := 0..16#ffff, product_id := 0..16#ffff, device_version := 0..16#ffff, manufacturer_string_index := byte(), product_string_index := byte(), serial_number_string_index := byte(), num_configurations := byte() }. %% A map representing the standard USB device descriptor. This descriptor is %% documented in section 9.6.1 of the USB 3.0 specification. -opaque device_handle() :: reference(). %% A referenced to an open USB device. This handle is used to perform I/O and %% other operations. When finished with a device handle, you should call %% {@link close_device/1}. % % Device List API % %% @doc Returns a list of USB devices currently attached to the system. -spec get_device_list() -> {ok, [device()]} | {error, term()}. get_device_list() -> get_device_list_nif(). %% nif get_device_list_nif() -> erlang:nif_error(not_loaded). %% @doc Get the number of the bus that a device is connected to. -spec get_bus_number(device()) -> {ok, 0..255} | {error, term()}. get_bus_number(Device) -> get_bus_number_nif(Device). %% nif get_bus_number_nif(_Device) -> erlang:nif_error(not_loaded). %% @doc Get the number of the port that a device is connected to. -spec get_port_number(device()) -> {ok, 0..255} | {error, term()}. get_port_number(Device) -> get_port_number_nif(Device). %%nif get_port_number_nif(_Device) -> erlang:nif_error(not_loaded). %% @doc Get the list of all port numbers from root for the specified device. -spec get_port_numbers(device()) -> {ok, [0..255]} | {error, term()}. get_port_numbers(Device) -> get_port_numbers_nif(Device). %% nif get_port_numbers_nif(_Device) -> erlang:nif_error(not_loaded). %% @doc Get the address of the device on the bus it is connected to. -spec get_device_address(device()) -> {ok, 0..255} | {error, term()}. get_device_address(Device) -> get_device_address_nif(Device). %% nif get_device_address_nif(_Device) -> erlang:nif_error(not_loaded). %% @doc Get the negotiated connection speed for a device. -spec get_device_speed(device()) -> {ok, device_speed()} | {error, term()}. get_device_speed(Device) -> get_device_speed_nif(Device). %% nif get_device_speed_nif(_Device) -> erlang:nif_error(not_loaded). % % Device API % %% @doc Get the USB device descriptor for a given device. -spec get_device_descriptor(device()) -> {ok, device_descriptor()} | {error, term()}. get_device_descriptor(Device) -> get_device_descriptor_nif(Device). %% nif get_device_descriptor_nif(_Device) -> erlang:nif_error(not_loaded). %% @doc Get a USB configuration descriptor based on its index. -spec get_config_descriptor(device(), byte()) -> {ok, config_descriptor()} | {error, term()}. -type config_descriptor() :: #{ configuration_number := byte(), num_interfaces := byte(), description_string_index := byte(), attributes := byte(), max_power := byte(), interfaces := [interface_descriptor()], extra := binary() }. -type interface_descriptor() :: #{ alt_settings := [alt_setting()] }. -type alt_setting() :: #{ interface_number := non_neg_integer(), setting_number := non_neg_integer(), class_code := non_neg_integer(), sub_class_code := non_neg_integer(), protocol_code := non_neg_integer(), description_string_index := non_neg_integer(), endpoints := [endpoint()], extra := binary() }. -type endpoint() :: #{ address := non_neg_integer(), attributes := non_neg_integer(), max_packet_size := non_neg_integer(), interval := non_neg_integer(), refresh := non_neg_integer(), synch_address := non_neg_integer(), extra := binary() }. get_config_descriptor(Device, ConfigIndex) -> get_config_descriptor_nif(Device, ConfigIndex). %% nif get_config_descriptor_nif(_Device, _ConfigIndex) -> erlang:nif_error(not_loaded). %% @doc Open a device and obtain a device handle. A handle allows you to perform %% I/O on the device in question. -spec open_device(device()) -> {ok, device_handle()} | {error, term()}. open_device(Device) -> open_device_nif(Device). open_device_nif(_Device) -> erlang:nif_error(not_loaded). %% @doc Close a device handle. -spec close_device(device_handle()) -> ok | {error, term()}. close_device(DeviceHandle) -> close_device_nif(DeviceHandle). close_device_nif(_DeviceHandle) -> erlang:nif_error(not_loaded). %% @doc Claim an interface on a given device handle. You must claim the %% interface you wish to use before you can perform I/O on any of its endpoints. %% %% It is legal to attempt to claim an already-claimed interface, in which case %% this function just returns `ok' without doing anything. %% %% Claiming of interfaces is a purely logical operation; it does not cause any %% requests to be sent over the bus. Interface claiming is used to instruct the %% underlying operating system that your application wishes to take ownership of %% the interface. -spec claim_interface(device_handle(), non_neg_integer()) -> ok | {error, term()}. claim_interface(DeviceHandle, InterfaceNumber) -> claim_interface_nif(DeviceHandle, InterfaceNumber). claim_interface_nif(_DeviceHandle, _InterfaceNumber) -> erlang:nif_error(not_loaded). %% @doc Release an interface previously claimed with {@link claim_interface/2}. %% %% You should release all claimed interfaces before closing a device handle. -spec release_interface(device_handle(), non_neg_integer()) -> ok | {error, term()}. release_interface(DeviceHandle, InterfaceNumber) -> release_interface_nif(DeviceHandle, InterfaceNumber). release_interface_nif(_DeviceHandle, _InterfaceNumber) -> erlang:nif_error(not_loaded). %% @doc Clear the halt/stall condition for an endpoint. %% %% Endpoints with halt status are unable to receive or transmit data until the %% halt condition is stalled. %% %% You should cancel all pending transfers before attempting to clear the halt %% condition. -spec clear_halt(device_handle(), byte()) -> ok | {error, term()}. clear_halt(DeviceHandle, Endpoint) -> clear_halt_nif(DeviceHandle, Endpoint). clear_halt_nif(_DeviceHandle, _Endpoint) -> erlang:nif_error(not_loaded). -spec set_configuration(device_handle(), integer()) -> ok | {error, term()}. set_configuration(DeviceHandle, Configuration) -> set_configuration_nif(DeviceHandle, Configuration). set_configuration_nif(_DeviceHandle, _Configuration) -> erlang:nif_error(not_loaded). -spec attach_kernel_driver(device_handle(), integer()) -> ok | {error, term()}. attach_kernel_driver(DeviceHandle, InterfaceNumber) -> attach_kernel_driver_nif(DeviceHandle, InterfaceNumber). attach_kernel_driver_nif(_DeviceHandle, _InterfaceNumber) -> erlang:nif_error(not_loaded). -spec detach_kernel_driver(device_handle(), integer()) -> ok | {error, term()}. detach_kernel_driver(DeviceHandle, InterfaceNumber) -> detach_kernel_driver_nif(DeviceHandle, InterfaceNumber). detach_kernel_driver_nif(_DeviceHandle, _InterfaceNumber) -> erlang:nif_error(not_loaded). -spec read_bulk(device_handle(), byte(), integer(), timeout()) -> {ok, binary()} | {error, timeout, binary()} | {error, term()}. read_bulk(DeviceHandle, Endpoint, DataLen, Timeout) -> read_bulk_nif(DeviceHandle, Endpoint, DataLen, nif_timeout(Timeout)). %% nif read_bulk_nif(_DeviceHandle, _Endpoint, _DataLen, _Timeout) -> erlang:nif_error(not_loaded). -spec write_bulk(device_handle(), byte(), binary(), timeout()) -> {ok, integer()} | {error, timeout, non_neg_integer()} | {error, term()}. write_bulk(DeviceHandle, Endpoint, Data, Timeout) -> write_bulk_nif(DeviceHandle, Endpoint, Data, nif_timeout(Timeout)). %% nif write_bulk_nif(_DeviceHandle, _Endpoint, _Data, _Timeout) -> erlang:nif_error(not_loaded). -spec read_interrupt(device_handle(), byte(), integer(), timeout()) -> {ok, binary()} | {error, timeout, binary()} | {error, term()}. read_interrupt(DeviceHandle, Endpoint, DataLen, Timeout) -> read_interrupt_nif(DeviceHandle, Endpoint, DataLen, nif_timeout(Timeout)). %% nif read_interrupt_nif(_DeviceHandle, _Endpoint, _DataLen, _Timeout) -> erlang:nif_error(not_loaded). -spec write_interrupt(device_handle(), byte(), binary(), timeout()) -> {ok, integer()} | {error, timeout, non_neg_integer()} | {error, term()}. write_interrupt(DeviceHandle, Endpoint, Data, Timeout) -> write_interrupt_nif(DeviceHandle, Endpoint, Data, nif_timeout(Timeout)). %% nif write_interrupt_nif(_DeviceHandle, _Endpoint, _Data, _Timeout) -> erlang:nif_error(not_loaded). -spec read_control(device_handle(), byte(), byte(), non_neg_integer(), non_neg_integer(), non_neg_integer(), timeout()) -> {ok, binary()} | {error, term()}. read_control(DeviceHandle, RequestType, Request, Value, Index, ReadLen, Timeout) -> read_control_nif(DeviceHandle, RequestType, Request, Value, Index, ReadLen, nif_timeout(Timeout)). %% nif read_control_nif(_DeviceHandle,_RequestType, _Request, _Value, _Index, _ReadLen, _Timeout) -> erlang:nif_error(not_loaded). -spec write_control(device_handle(), byte(), byte(), non_neg_integer(), non_neg_integer(), binary(), timeout()) -> {ok, integer()} | {error, term()}. write_control(DeviceHandle, RequestType, Request, Value, Index, Data, Timeout) -> write_control_nif(DeviceHandle, RequestType, Request, Value, Index, Data, nif_timeout(Timeout)). %% nif write_control_nif(_DeviceHandle,_RequestType, _Request, _Value, _Index, _Data, _Timeout) -> erlang:nif_error(not_loaded). % % Hotplug API % %% @doc start listening for hotplug events. %% %% @equiv monitor_hotplug([]) -spec monitor_hotplug() -> {ok, hotplug_monitor()} | {error, term()}. -opaque hotplug_monitor() :: reference(). monitor_hotplug() -> monitor_hotplug([]). %% @doc start listening for hotplug events. The owner process will start %% receiving the following hotplug events: %% %% `{usb, device_arrived, Device}' and %% `{usb, device_left, Device}'. %% %% If the `enumerate' flag is provided, the owner process will received %% `device_arrived' events for all devices already plugged into the host. -spec monitor_hotplug([monitor_hotplug_flag()]) -> {ok, hotplug_monitor()} | {error, term()}. -type monitor_hotplug_flag() :: enumerate. monitor_hotplug(Flags) -> monitor_hotplug_nif(lists:foldl(fun (enumerate, Acc) -> Acc bor (1 bsl 0) end, 0, Flags)). %% nif monitor_hotplug_nif(_Flags) -> erlang:nif_error(not_loaded). %% @doc Stop listening for hotplug events. -spec demonitor_hotplug(hotplug_monitor()) -> ok | {error, term()}. demonitor_hotplug(HotplugEvents) -> demonitor_hotplug_nif(HotplugEvents). %% nif demonitor_hotplug_nif(_HotplugEvents) -> erlang:nif_error(not_loaded). % % Helpers % nif_timeout(infinity) -> 0; nif_timeout(0) -> 1; nif_timeout(N) -> N. % % Initialization % init() -> case nif_path() of undefined -> ok; Path -> ok = erlang:load_nif(Path, 0) end. -spec nif_path() -> string() | binary() | undefined. nif_path() -> Priv = case code:priv_dir(usb) of {error, bad_name} -> case code:which(?MODULE) of File when is_list(File) -> filename:join([filename:dirname(File), "../priv"]); _ -> "../priv" end; Dir -> Dir end, nif_path(os:type(), Priv). nif_path(_, Dir) -> filename:join([Dir, "usb_nif"]).