1use rustix::io::Errno;
4use std::{
5 collections::HashMap,
6 ffi::{OsStr, OsString},
7 fmt, fs,
8 io::{Error, ErrorKind, Result},
9 os::unix::{
10 fs::symlink,
11 prelude::{OsStrExt, OsStringExt},
12 },
13 path::{Path, PathBuf},
14};
15
16use crate::{
17 configfs_dir, function,
18 function::{
19 util::{call_remove_handler, init_remove_handlers},
20 Handle,
21 },
22 hex_u16, hex_u8,
23 lang::Language,
24 request_module, trim_os_str,
25 udc::Udc,
26 write_attr, Speed,
27};
28
29pub const GADGET_IOC_MAGIC: u8 = b'g';
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct Class {
35 pub class: u8,
37 pub sub_class: u8,
39 pub protocol: u8,
41}
42
43impl Class {
44 pub const VENDOR_SPECIFIC: u8 = 0xff;
46
47 pub const fn new(class: u8, sub_class: u8, protocol: u8) -> Self {
49 Self { class, sub_class, protocol }
50 }
51
52 pub const fn vendor_specific(sub_class: u8, protocol: u8) -> Self {
54 Self::new(Self::VENDOR_SPECIFIC, sub_class, protocol)
55 }
56
57 #[deprecated(since = "1.1.0", note = "use Class::INTERFACE_SPECIFIC instead")]
64 pub const fn interface_specific() -> Self {
65 Self::new(0, 0, 0)
66 }
67
68 pub const INTERFACE_SPECIFIC: Self = Self::new(0x00, 0x00, 0x00);
74
75 pub const MISCELLANEOUS_IAD: Self = Self::new(0xEF, 0x02, 0x01);
80
81 pub const AUDIO_CONTROL: Self = Self::new(0x01, 0x01, 0x00);
85 pub const AUDIO_STREAMING: Self = Self::new(0x01, 0x02, 0x00);
87 pub const AUDIO_MIDISTREAMING: Self = Self::new(0x01, 0x03, 0x00);
89
90 pub const CDC_ACM: Self = Self::new(0x02, 0x02, 0x01);
94 pub const CDC_ECM: Self = Self::new(0x02, 0x06, 0x00);
96 pub const CDC_EEM: Self = Self::new(0x02, 0x0C, 0x07);
98 pub const CDC_NCM: Self = Self::new(0x02, 0x0D, 0x00);
100 pub const CDC_DATA: Self = Self::new(0x0A, 0x00, 0x00);
102
103 pub const HID: Self = Self::new(0x03, 0x00, 0x00);
107 pub const HID_BOOT_KEYBOARD: Self = Self::new(0x03, 0x01, 0x01);
109 pub const HID_BOOT_MOUSE: Self = Self::new(0x03, 0x01, 0x02);
111
112 pub const PRINTER_UNIDIRECTIONAL: Self = Self::new(0x07, 0x01, 0x01);
116 pub const PRINTER_BIDIRECTIONAL: Self = Self::new(0x07, 0x01, 0x02);
118
119 pub const MASS_STORAGE_SCSI_BULK: Self = Self::new(0x08, 0x06, 0x50);
123
124 pub const VIDEO_CONTROL: Self = Self::new(0x0E, 0x01, 0x00);
128 pub const VIDEO_STREAMING: Self = Self::new(0x0E, 0x02, 0x00);
130
131 pub const DFU_RUNTIME: Self = Self::new(0xFE, 0x01, 0x01);
135 pub const DFU_MODE: Self = Self::new(0xFE, 0x01, 0x02);
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub struct Id {
142 pub vendor: u16,
144 pub product: u16,
146}
147
148impl Id {
149 pub const fn new(vendor: u16, product: u16) -> Self {
151 Self { vendor, product }
152 }
153
154 pub const LINUX_FOUNDATION_VID: u16 = 0x1d6b;
160
161 pub const LINUX_FOUNDATION_SERIAL: Self = Self::new(0x1d6b, 0x0101);
165 pub const LINUX_FOUNDATION_ETHERNET: Self = Self::new(0x1d6b, 0x0102);
169 pub const LINUX_FOUNDATION_STORAGE: Self = Self::new(0x1d6b, 0x0103);
173 pub const LINUX_FOUNDATION_COMPOSITE: Self = Self::new(0x1d6b, 0x0104);
177 pub const LINUX_FOUNDATION_FFS: Self = Self::new(0x1d6b, 0x0105);
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
185pub struct Strings {
186 pub manufacturer: String,
188 pub product: String,
190 pub serial_number: String,
196}
197
198impl Strings {
199 pub fn new(manufacturer: impl AsRef<str>, product: impl AsRef<str>, serial_number: impl AsRef<str>) -> Self {
203 Self {
204 manufacturer: manufacturer.as_ref().to_string(),
205 product: product.as_ref().to_string(),
206 serial_number: serial_number.as_ref().to_string(),
207 }
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
213pub struct OsDescriptor {
214 pub vendor_code: u8,
216 pub qw_sign: String,
218 pub config: usize,
223}
224
225impl OsDescriptor {
226 pub const fn new(vendor_code: u8, qw_sign: String) -> Self {
228 Self { vendor_code, qw_sign, config: 0 }
229 }
230
231 pub fn microsoft() -> Self {
235 Self { vendor_code: 0xf0, qw_sign: "MSFT100".to_string(), config: 0 }
236 }
237}
238
239#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
241pub enum WebUsbVersion {
242 #[default]
244 V10,
245 Other(u16),
247}
248
249impl From<WebUsbVersion> for u16 {
250 fn from(value: WebUsbVersion) -> Self {
251 match value {
252 WebUsbVersion::V10 => 0x0100,
253 WebUsbVersion::Other(ver) => ver,
254 }
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
260pub struct WebUsb {
261 pub version: WebUsbVersion,
263 pub vendor_code: u8,
265 pub landing_page: String,
267}
268
269impl WebUsb {
270 pub fn new(vendor_code: u8, landing_page: impl AsRef<str>) -> Self {
272 Self { version: WebUsbVersion::default(), vendor_code, landing_page: landing_page.as_ref().to_string() }
273 }
274}
275
276#[derive(Debug, Clone)]
278#[non_exhaustive]
279pub struct Config {
280 pub max_power: u16,
282 pub self_powered: bool,
284 pub remote_wakeup: bool,
286 pub description: HashMap<Language, String>,
288 pub functions: Vec<function::Handle>,
290}
291
292impl Config {
293 pub fn new(description: impl AsRef<str>) -> Self {
295 Self {
296 max_power: 500,
297 self_powered: false,
298 remote_wakeup: false,
299 description: [(Language::default(), description.as_ref().to_string())].into(),
300 functions: Default::default(),
301 }
302 }
303
304 pub fn add_function(&mut self, function_handle: function::Handle) {
306 self.functions.push(function_handle);
307 }
308
309 #[must_use]
311 pub fn with_function(mut self, function_handle: function::Handle) -> Self {
312 self.add_function(function_handle);
313 self
314 }
315
316 fn register(
317 &self, gadget_dir: &Path, idx: usize, func_dirs: &HashMap<function::Handle, PathBuf>,
318 ) -> Result<PathBuf> {
319 let dir = gadget_dir.join("configs").join(format!("c.{idx}"));
320 log::debug!("creating config at {}", dir.display());
321 fs::create_dir(&dir)?;
322
323 let mut attributes = 1 << 7;
324 if self.self_powered {
325 attributes |= 1 << 6;
326 }
327 if self.remote_wakeup {
328 attributes |= 1 << 5;
329 }
330
331 write_attr(&dir.join("bmAttributes"), hex_u8(attributes))?;
332 write_attr(&dir.join("MaxPower"), self.max_power.to_string())?;
333
334 for (&lang, desc) in &self.description {
335 let lang_dir = dir.join("strings").join(hex_u16(lang.into()));
336 fs::create_dir(&lang_dir)?;
337 write_attr(&lang_dir.join("configuration"), desc)?;
338 }
339
340 for func in &self.functions {
341 let func_dir = &func_dirs[func];
342 log::debug!("adding function {}", func_dir.display());
343 symlink(func_dir, dir.join(func_dir.file_name().unwrap()))?;
344 }
345
346 Ok(dir)
347 }
348}
349
350#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
352pub enum UsbVersion {
353 V11,
355 #[default]
357 V20,
358 V21,
360 V30,
362 V31,
364 Other(u16),
366}
367
368impl From<UsbVersion> for u16 {
369 fn from(value: UsbVersion) -> Self {
370 match value {
371 UsbVersion::V11 => 0x0110,
372 UsbVersion::V20 => 0x0200,
373 UsbVersion::V21 => 0x0201,
374 UsbVersion::V30 => 0x0300,
375 UsbVersion::V31 => 0x0310,
376 UsbVersion::Other(ver) => ver,
377 }
378 }
379}
380
381#[derive(Debug, Clone)]
385#[non_exhaustive]
386pub struct Gadget {
387 pub name: Option<String>,
391 pub device_class: Class,
393 pub id: Id,
395 pub strings: HashMap<Language, Strings>,
397 pub max_packet_size0: u8,
399 pub device_release: u16,
403 pub usb_version: UsbVersion,
405 pub max_speed: Option<Speed>,
407 pub os_descriptor: Option<OsDescriptor>,
411 pub web_usb: Option<WebUsb>,
415 pub configs: Vec<Config>,
417 pub keep_on_error: bool,
423}
424
425impl Gadget {
426 pub fn new(device_class: Class, id: Id, strings: Strings) -> Self {
428 Self {
429 name: None,
430 device_class,
431 id,
432 strings: [(Language::default(), strings)].into(),
433 max_packet_size0: 64,
434 device_release: 0x0000,
435 usb_version: UsbVersion::default(),
436 max_speed: None,
437 os_descriptor: None,
438 web_usb: None,
439 configs: Vec::new(),
440 keep_on_error: false,
441 }
442 }
443
444 pub fn add_config(&mut self, config: Config) {
446 self.configs.push(config);
447 }
448
449 #[must_use]
451 pub fn with_config(mut self, config: Config) -> Self {
452 self.add_config(config);
453 self
454 }
455
456 #[must_use]
458 pub fn with_os_descriptor(mut self, os_descriptor: OsDescriptor) -> Self {
459 self.os_descriptor = Some(os_descriptor);
460 self
461 }
462
463 #[must_use]
465 pub fn with_web_usb(mut self, web_usb: WebUsb) -> Self {
466 self.web_usb = Some(web_usb);
467 self
468 }
469
470 #[must_use = "consumes the gadget"]
478 pub fn register(self) -> Result<RegGadget> {
479 if self.configs.is_empty() {
480 return Err(Error::new(ErrorKind::InvalidInput, "USB gadget must have at least one configuration"));
481 }
482
483 let usb_gadget_dir = usb_gadget_dir()?;
484
485 let dir = if let Some(ref name) = self.name {
486 let dir = usb_gadget_dir.join(name);
487 fs::create_dir(&dir)?;
488 dir
489 } else {
490 let mut gadget_idx: u16 = 0;
491 loop {
492 let dir = usb_gadget_dir.join(format!("usb-gadget{gadget_idx}"));
493 match fs::create_dir(&dir) {
494 Ok(()) => break dir,
495 Err(err) if err.kind() == ErrorKind::AlreadyExists => (),
496 Err(err) => return Err(err),
497 }
498 gadget_idx = gadget_idx
499 .checked_add(1)
500 .ok_or_else(|| Error::new(ErrorKind::OutOfMemory, "USB gadgets exhausted"))?;
501 }
502 };
503
504 log::debug!("registering gadget at {}", dir.display());
505
506 let mut reg = RegGadget { dir, attached: true, func_dirs: HashMap::new() };
508 if let Err(err) = self.populate(&mut reg) {
509 if self.keep_on_error {
510 log::warn!("keeping partially registered gadget at {} for debugging", reg.dir.display());
511 reg.detach();
512 }
513 return Err(err);
514 }
515
516 log::debug!("gadget at {} registered", reg.dir.display());
517 Ok(reg)
518 }
519
520 fn populate(&self, reg: &mut RegGadget) -> Result<()> {
522 let dir = reg.dir.clone();
523
524 write_attr(&dir.join("bDeviceClass"), hex_u8(self.device_class.class))?;
525 write_attr(&dir.join("bDeviceSubClass"), hex_u8(self.device_class.sub_class))?;
526 write_attr(&dir.join("bDeviceProtocol"), hex_u8(self.device_class.protocol))?;
527
528 write_attr(&dir.join("idVendor"), hex_u16(self.id.vendor))?;
529 write_attr(&dir.join("idProduct"), hex_u16(self.id.product))?;
530
531 write_attr(&dir.join("bMaxPacketSize0"), hex_u8(self.max_packet_size0))?;
532 write_attr(&dir.join("bcdDevice"), hex_u16(self.device_release))?;
533 write_attr(&dir.join("bcdUSB"), hex_u16(self.usb_version.into()))?;
534
535 if let Some(v) = self.max_speed {
536 write_attr(&dir.join("max_speed"), v.to_string())?;
537 }
538
539 if let Some(webusb) = &self.web_usb {
540 let webusb_dir = dir.join("webusb");
541 if webusb_dir.is_dir() {
542 write_attr(&webusb_dir.join("bVendorCode"), hex_u8(webusb.vendor_code))?;
543 write_attr(&webusb_dir.join("bcdVersion"), hex_u16(webusb.version.into()))?;
544 write_attr(&webusb_dir.join("landingPage"), &webusb.landing_page)?;
545 write_attr(&webusb_dir.join("use"), "1")?;
546 } else {
547 log::warn!("WebUSB descriptor is unsupported by kernel");
548 }
549 }
550
551 for (&lang, strs) in &self.strings {
552 let lang_dir = dir.join("strings").join(hex_u16(lang.into()));
553 fs::create_dir(&lang_dir)?;
554
555 write_attr(&lang_dir.join("manufacturer"), &strs.manufacturer)?;
556 write_attr(&lang_dir.join("product"), &strs.product)?;
557 write_attr(&lang_dir.join("serialnumber"), &strs.serial_number)?;
558 }
559
560 let gadget_name = dir.file_name().unwrap().to_string_lossy();
561 let mut functions = Vec::new();
562 for func in self.configs.iter().flat_map(|c| &c.functions) {
563 if !functions.contains(&func) {
564 functions.push(func);
565 }
566 }
567 for (func_idx, &func) in functions.iter().enumerate() {
568 let func_dir = dir.join(
569 dir.join("functions")
570 .join(format!("{}.{gadget_name}-{func_idx}", func.get().driver().to_string_lossy())),
571 );
572 log::debug!("creating function at {}", func_dir.display());
573 fs::create_dir(&func_dir)?;
574
575 func.get().dir().set_dir(&func_dir);
577 reg.func_dirs.insert(func.clone(), func_dir);
578 func.get().register()?;
579 }
580
581 let mut config_dirs = Vec::new();
582 for (idx, config) in self.configs.iter().enumerate() {
583 let dir = config.register(&dir, idx + 1, ®.func_dirs)?;
584 config_dirs.push(dir);
585 }
586
587 if let Some(os_desc) = &self.os_descriptor {
588 let os_desc_dir = dir.join("os_desc");
589 if os_desc_dir.is_dir() {
590 write_attr(&os_desc_dir.join("b_vendor_code"), hex_u8(os_desc.vendor_code))?;
591 write_attr(&os_desc_dir.join("qw_sign"), &os_desc.qw_sign)?;
592 write_attr(&os_desc_dir.join("use"), "1")?;
593
594 let config_dir = config_dirs.get(os_desc.config).ok_or_else(|| {
595 Error::new(ErrorKind::InvalidInput, "invalid configuration index in OS descriptor")
596 })?;
597 log::debug!("linking OS descriptor to config dir {}", config_dir.display());
598 symlink(config_dir, os_desc_dir.join(config_dir.file_name().unwrap()))?;
599 } else {
600 log::warn!("USB OS descriptor is unsupported by kernel");
601 }
602 }
603
604 Ok(())
605 }
606
607 #[must_use = "consumes the gadget"]
615 pub fn bind(self, udc: &Udc) -> Result<RegGadget> {
616 let keep_on_error = self.keep_on_error;
617 let mut reg = self.register()?;
618 if let Err(err) = reg.bind(Some(udc)) {
619 if keep_on_error {
620 log::warn!("keeping gadget at {} that failed to bind for debugging", reg.dir.display());
621 reg.detach();
622 }
623 return Err(err);
624 }
625 Ok(reg)
626 }
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
633pub struct RegFunction {
634 driver: String,
636 instance: String,
638}
639
640impl RegFunction {
641 pub fn driver(&self) -> &str {
643 &self.driver
644 }
645
646 pub fn instance(&self) -> &str {
648 &self.instance
649 }
650}
651
652impl fmt::Display for RegFunction {
653 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
654 write!(f, "{}.{}", self.driver, self.instance)
655 }
656}
657
658#[must_use = "The USB gadget is removed when RegGadget is dropped."]
663pub struct RegGadget {
664 dir: PathBuf,
665 attached: bool,
666 func_dirs: HashMap<Handle, PathBuf>,
667}
668
669impl fmt::Debug for RegGadget {
670 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
671 f.debug_struct("RegGadget").field("name", &self.name()).field("is_attached", &self.is_attached()).finish()
672 }
673}
674
675impl RegGadget {
676 pub fn name(&self) -> &OsStr {
678 self.dir.file_name().unwrap()
679 }
680
681 pub fn path(&self) -> &Path {
683 &self.dir
684 }
685
686 pub fn is_attached(&self) -> bool {
688 self.attached
689 }
690
691 pub fn udc(&self) -> Result<Option<OsString>> {
693 let data = OsString::from_vec(fs::read(self.dir.join("UDC"))?);
694 let data = trim_os_str(&data);
695 if data.is_empty() {
696 Ok(None)
697 } else {
698 Ok(Some(data.to_os_string()))
699 }
700 }
701
702 pub fn functions(&self) -> Result<Vec<RegFunction>> {
704 let func_dir = self.dir.join("functions");
705 let mut functions = Vec::new();
706
707 if let Ok(entries) = fs::read_dir(&func_dir) {
708 for entry in entries {
709 let Ok(entry) = entry else { continue };
710 if !entry.metadata()?.is_dir() {
711 continue;
712 }
713 let name = entry.file_name().to_string_lossy().to_string();
714 if let Some((driver, instance)) = name.split_once('.') {
715 functions.push(RegFunction { driver: driver.to_string(), instance: instance.to_string() });
716 }
717 }
718 }
719
720 functions.sort();
721 Ok(functions)
722 }
723
724 pub fn bind(&self, udc: Option<&Udc>) -> Result<()> {
728 log::debug!("binding gadget {:?} to {:?}", self, udc);
729
730 let name = match udc {
731 Some(udc) => udc.name().to_os_string(),
732 None => "\n".into(),
733 };
734
735 match fs::write(self.dir.join("UDC"), name.as_bytes()) {
736 Ok(()) => (),
737 Err(err) if udc.is_none() && err.raw_os_error() == Some(Errno::NODEV.raw_os_error()) => (),
738 Err(err) => return Err(err),
739 }
740
741 for func in self.func_dirs.keys() {
742 func.get().dir().set_bound(udc.is_some());
743 }
744
745 Ok(())
746 }
747
748 pub fn detach(&mut self) {
752 self.attached = false;
753 }
754
755 fn do_remove(&mut self) -> Result<()> {
756 for func in self.func_dirs.keys() {
757 func.get().pre_removal()?;
758 }
759
760 for func in self.func_dirs.keys() {
761 func.get().dir().set_bound(false);
762 }
763
764 remove_at(&self.dir)?;
765
766 for func in self.func_dirs.keys() {
767 func.get().dir().reset_dir();
768 }
769
770 for (func, dir) in &self.func_dirs {
771 func.get().post_removal(dir)?;
772 }
773
774 self.detach();
775 Ok(())
776 }
777
778 pub fn remove(mut self) -> Result<()> {
780 self.do_remove()
781 }
782}
783
784impl Drop for RegGadget {
785 fn drop(&mut self) {
786 if self.attached {
787 if let Err(err) = self.do_remove() {
788 log::warn!("removing gadget at {} failed: {err}", self.dir.display());
789 }
790 }
791 }
792}
793
794fn remove_at(dir: &Path) -> Result<()> {
796 log::debug!("removing gadget at {}", dir.display());
797
798 init_remove_handlers();
799
800 let _ = fs::write(dir.join("UDC"), "\n");
801
802 if let Ok(entries) = fs::read_dir(dir.join("os_desc")) {
803 for file in entries {
804 let Ok(file) = file else { continue };
805 let Ok(file_type) = file.file_type() else { continue };
806 if file_type.is_symlink() {
807 fs::remove_file(file.path())?;
808 }
809 }
810 }
811
812 for config_dir in fs::read_dir(dir.join("configs"))? {
813 let Ok(config_dir) = config_dir else { continue };
814 if !config_dir.metadata()?.is_dir() {
815 continue;
816 }
817
818 for func in fs::read_dir(config_dir.path())? {
819 let Ok(func) = func else { continue };
820 if func.metadata()?.is_symlink() {
821 fs::remove_file(func.path())?;
822 }
823 }
824
825 for lang in fs::read_dir(config_dir.path().join("strings"))? {
826 let Ok(lang) = lang else { continue };
827 if lang.metadata()?.is_dir() {
828 fs::remove_dir(lang.path())?;
829 }
830 }
831
832 fs::remove_dir(config_dir.path())?;
833 }
834
835 for func_dir in fs::read_dir(dir.join("functions"))? {
836 let Ok(func_dir) = func_dir else { continue };
837 if !func_dir.metadata()?.is_dir() {
838 continue;
839 }
840
841 call_remove_handler(&func_dir.path())?;
842
843 fs::remove_dir(func_dir.path())?;
844 }
845
846 for lang in fs::read_dir(dir.join("strings"))? {
847 let Ok(lang) = lang else { continue };
848 if lang.metadata()?.is_dir() {
849 fs::remove_dir(lang.path())?;
850 }
851 }
852
853 fs::remove_dir(dir)?;
854
855 log::debug!("removed gadget at {}", dir.display());
856 Ok(())
857}
858
859fn usb_gadget_dir() -> Result<PathBuf> {
861 let _ = request_module("libcomposite");
862
863 let usb_gadget_dir = configfs_dir()?.join("usb_gadget");
864 if usb_gadget_dir.is_dir() {
865 Ok(usb_gadget_dir)
866 } else {
867 Err(Error::new(ErrorKind::NotFound, "usb_gadget not found in configfs"))
868 }
869}
870
871pub fn registered() -> Result<Vec<RegGadget>> {
876 let usb_gadget_dir = usb_gadget_dir()?;
877
878 let mut gadgets = Vec::new();
879 for gadget_dir in fs::read_dir(usb_gadget_dir)? {
880 let Ok(gadget_dir) = gadget_dir else { continue };
881 if gadget_dir.metadata()?.is_dir() {
882 gadgets.push(RegGadget { dir: gadget_dir.path(), attached: false, func_dirs: HashMap::new() });
883 }
884 }
885
886 Ok(gadgets)
887}
888
889pub fn remove_all() -> Result<()> {
894 let mut res = Ok(());
895
896 for gadget in registered()? {
897 if let Err(err) = gadget.remove() {
898 res = Err(err);
899 }
900 }
901
902 res
903}
904
905pub fn unbind_all() -> Result<()> {
910 let mut res = Ok(());
911
912 for gadget in registered()? {
913 if let Err(err) = gadget.bind(None) {
914 res = Err(err);
915 }
916 }
917
918 res
919}