USB gadget (device-side) definition over configfs.
The mirror image of host-side USB (see the bodge_usb library): instead of
talking to a USB device, this machine is one. A gadget is described by a
spec (IDs, strings, functions, configurations), materialized as a configfs
tree, and bound to a UDC (USB device controller). The kernel's function
drivers (usb_f_hid, usb_f_acm, usb_f_ecm, usb_f_mass_storage, ...)
implement the class protocol; chardev-backed functions (HID, serial) are
then driven through any file API.
Pure filesystem plumbing: no processes, no hidden state. A t/0 is just
the gadget's name and configfs path.
spec = %{
vendor_id: 0xCAFE,
product_id: 0xBABE,
strings: %{manufacturer: "bodge", product: "demo", serialnumber: "g-1"},
functions: %{
"hid.usb0" => %{
protocol: 0,
subclass: 0,
report_length: 8,
report_desc: <<0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, ...>>
}
},
configs: %{
"c.1" => %{configuration: "demo", max_power: 120, functions: ["hid.usb0"]}
}
}
{:ok, g} = BodgeUSBGadget.define("demo", spec)
:ok = BodgeUSBGadget.bind(g, "dummy_udc.0")
{:ok, "/dev/hidg0"} = BodgeUSBGadget.device_node(g, "hid.usb0")
...
:ok = BodgeUSBGadget.unbind(g)
:ok = BodgeUSBGadget.remove(g)Requirements: a UDC (/sys/class/udc non-empty; OTG-capable hardware or
dummy_hcd), configfs mounted, libcomposite plus the usb_f_* modules
for the functions used, and permissions on /sys/kernel/config (root).
Function attribute values: integers are written in decimal, binaries raw
(e.g. report_desc), booleans as 1/0. max_power is in mA. os_desc
(Windows compatibility descriptors) is not covered; for fully custom device
functions see BodgeUSBGadget.FunctionFs.
Summary
Types
Gadget description. functions maps "type.instance" names to attribute
maps; configs maps "label.N" names to %{configuration: String.t(), max_power: mA, functions: [function_name]}. Extra raw gadget attributes
(e.g. "bDeviceClass") go under :attrs.
A defined gadget: its configfs directory.
Functions
Bind the gadget to a UDC, making it appear on the bus. udc defaults to the
first controller in udcs/0.
Create the configfs tree for name from spec. Returns {:ok, gadget};
on any failure the partial tree is torn down again and {:error, reason}
is returned, where reason is an atom or a {tag, detail} tuple (e.g.
{:function_failed, {"hid.usb0", :enoent}}). Fails with
{:error, :already_defined} if a gadget of that name exists.
opts[:root] overrides the configfs root (tests).
The /dev node backing a chardev function, resolved through the function's
dev attribute (major:minor -> /sys/dev/char), with the acm port
number as a fallback. E.g. {:ok, "/dev/hidg0"} for "hid.usb0".
The network interface name created by an ethernet-style function
(ecm/ncm/rndis/eem), from its ifname attribute.
Unbind (if bound) and delete the gadget's configfs tree. configfs requires a
strict order (config symlinks, then config/function/string directories, then
the gadget itself); every step is best-effort so a half-built tree is also
removed. Returns :ok once the gadget directory is gone.
The UDC the gadget is bound to, or :unbound.
List the available UDCs (/sys/class/udc).
Unbind the gadget from its UDC (disconnect). Idempotent.
Types
@type spec() :: %{ optional(:vendor_id) => 0..65535, optional(:product_id) => 0..65535, optional(:bcd_usb) => 0..65535, optional(:bcd_device) => 0..65535, optional(:attrs) => %{optional(String.t()) => term()}, optional(:strings) => %{optional(atom()) => String.t()}, optional(:functions) => %{optional(String.t()) => map()}, optional(:configs) => %{optional(String.t()) => map()} }
Gadget description. functions maps "type.instance" names to attribute
maps; configs maps "label.N" names to %{configuration: String.t(), max_power: mA, functions: [function_name]}. Extra raw gadget attributes
(e.g. "bDeviceClass") go under :attrs.
A defined gadget: its configfs directory.
Functions
Bind the gadget to a UDC, making it appear on the bus. udc defaults to the
first controller in udcs/0.
Create the configfs tree for name from spec. Returns {:ok, gadget};
on any failure the partial tree is torn down again and {:error, reason}
is returned, where reason is an atom or a {tag, detail} tuple (e.g.
{:function_failed, {"hid.usb0", :enoent}}). Fails with
{:error, :already_defined} if a gadget of that name exists.
opts[:root] overrides the configfs root (tests).
The /dev node backing a chardev function, resolved through the function's
dev attribute (major:minor -> /sys/dev/char), with the acm port
number as a fallback. E.g. {:ok, "/dev/hidg0"} for "hid.usb0".
The network interface name created by an ethernet-style function
(ecm/ncm/rndis/eem), from its ifname attribute.
Unbind (if bound) and delete the gadget's configfs tree. configfs requires a
strict order (config symlinks, then config/function/string directories, then
the gadget itself); every step is best-effort so a half-built tree is also
removed. Returns :ok once the gadget directory is gone.
The UDC the gadget is bound to, or :unbound.
@spec udcs() :: [String.t()]
List the available UDCs (/sys/class/udc).
Unbind the gadget from its UDC (disconnect). Idempotent.