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 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,
192}
193
194impl Strings {
195 pub fn new(manufacturer: impl AsRef<str>, product: impl AsRef<str>, serial_number: impl AsRef<str>) -> Self {
197 Self {
198 manufacturer: manufacturer.as_ref().to_string(),
199 product: product.as_ref().to_string(),
200 serial_number: serial_number.as_ref().to_string(),
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
207pub struct OsDescriptor {
208 pub vendor_code: u8,
210 pub qw_sign: String,
212 pub config: usize,
217}
218
219impl OsDescriptor {
220 pub const fn new(vendor_code: u8, qw_sign: String) -> Self {
222 Self { vendor_code, qw_sign, config: 0 }
223 }
224
225 pub fn microsoft() -> Self {
229 Self { vendor_code: 0xf0, qw_sign: "MSFT100".to_string(), config: 0 }
230 }
231}
232
233#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
235pub enum WebUsbVersion {
236 #[default]
238 V10,
239 Other(u16),
241}
242
243impl From<WebUsbVersion> for u16 {
244 fn from(value: WebUsbVersion) -> Self {
245 match value {
246 WebUsbVersion::V10 => 0x0100,
247 WebUsbVersion::Other(ver) => ver,
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254pub struct WebUsb {
255 pub version: WebUsbVersion,
257 pub vendor_code: u8,
259 pub landing_page: String,
261}
262
263impl WebUsb {
264 pub fn new(vendor_code: u8, landing_page: impl AsRef<str>) -> Self {
266 Self { version: WebUsbVersion::default(), vendor_code, landing_page: landing_page.as_ref().to_string() }
267 }
268}
269
270#[derive(Debug, Clone)]
272#[non_exhaustive]
273pub struct Config {
274 pub max_power: u16,
276 pub self_powered: bool,
278 pub remote_wakeup: bool,
280 pub description: HashMap<Language, String>,
282 pub functions: Vec<function::Handle>,
284}
285
286impl Config {
287 pub fn new(description: impl AsRef<str>) -> Self {
289 Self {
290 max_power: 500,
291 self_powered: false,
292 remote_wakeup: false,
293 description: [(Language::default(), description.as_ref().to_string())].into(),
294 functions: Default::default(),
295 }
296 }
297
298 pub fn add_function(&mut self, function_handle: function::Handle) {
300 self.functions.push(function_handle);
301 }
302
303 #[must_use]
305 pub fn with_function(mut self, function_handle: function::Handle) -> Self {
306 self.add_function(function_handle);
307 self
308 }
309
310 fn register(
311 &self, gadget_dir: &Path, idx: usize, func_dirs: &HashMap<function::Handle, PathBuf>,
312 ) -> Result<PathBuf> {
313 let dir = gadget_dir.join("configs").join(format!("c.{idx}"));
314 log::debug!("creating config at {}", dir.display());
315 fs::create_dir(&dir)?;
316
317 let mut attributes = 1 << 7;
318 if self.self_powered {
319 attributes |= 1 << 6;
320 }
321 if self.remote_wakeup {
322 attributes |= 1 << 5;
323 }
324
325 fs::write(dir.join("bmAttributes"), hex_u8(attributes))?;
326 fs::write(dir.join("MaxPower"), self.max_power.to_string())?;
327
328 for (&lang, desc) in &self.description {
329 let lang_dir = dir.join("strings").join(hex_u16(lang.into()));
330 fs::create_dir(&lang_dir)?;
331 fs::write(lang_dir.join("configuration"), desc)?;
332 }
333
334 for func in &self.functions {
335 let func_dir = &func_dirs[func];
336 log::debug!("adding function {}", func_dir.display());
337 symlink(func_dir, dir.join(func_dir.file_name().unwrap()))?;
338 }
339
340 Ok(dir)
341 }
342}
343
344#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
346pub enum UsbVersion {
347 V11,
349 #[default]
351 V20,
352 V21,
354 V30,
356 V31,
358 Other(u16),
360}
361
362impl From<UsbVersion> for u16 {
363 fn from(value: UsbVersion) -> Self {
364 match value {
365 UsbVersion::V11 => 0x0110,
366 UsbVersion::V20 => 0x0200,
367 UsbVersion::V21 => 0x0201,
368 UsbVersion::V30 => 0x0300,
369 UsbVersion::V31 => 0x0310,
370 UsbVersion::Other(ver) => ver,
371 }
372 }
373}
374
375#[derive(Debug, Clone)]
379#[non_exhaustive]
380pub struct Gadget {
381 pub name: Option<String>,
385 pub device_class: Class,
387 pub id: Id,
389 pub strings: HashMap<Language, Strings>,
391 pub max_packet_size0: u8,
393 pub device_release: u16,
397 pub usb_version: UsbVersion,
399 pub max_speed: Option<Speed>,
401 pub os_descriptor: Option<OsDescriptor>,
403 pub web_usb: Option<WebUsb>,
405 pub configs: Vec<Config>,
407}
408
409impl Gadget {
410 pub fn new(device_class: Class, id: Id, strings: Strings) -> Self {
412 Self {
413 name: None,
414 device_class,
415 id,
416 strings: [(Language::default(), strings)].into(),
417 max_packet_size0: 64,
418 device_release: 0x0000,
419 usb_version: UsbVersion::default(),
420 max_speed: None,
421 os_descriptor: None,
422 web_usb: None,
423 configs: Vec::new(),
424 }
425 }
426
427 pub fn add_config(&mut self, config: Config) {
429 self.configs.push(config);
430 }
431
432 #[must_use]
434 pub fn with_config(mut self, config: Config) -> Self {
435 self.add_config(config);
436 self
437 }
438
439 #[must_use]
441 pub fn with_os_descriptor(mut self, os_descriptor: OsDescriptor) -> Self {
442 self.os_descriptor = Some(os_descriptor);
443 self
444 }
445
446 #[must_use]
448 pub fn with_web_usb(mut self, web_usb: WebUsb) -> Self {
449 self.web_usb = Some(web_usb);
450 self
451 }
452
453 #[must_use = "consumes the gadget"]
458 pub fn register(self) -> Result<RegGadget> {
459 if self.configs.is_empty() {
460 return Err(Error::new(ErrorKind::InvalidInput, "USB gadget must have at least one configuration"));
461 }
462
463 let usb_gadget_dir = usb_gadget_dir()?;
464
465 let dir = if let Some(ref name) = self.name {
466 let dir = usb_gadget_dir.join(name);
467 fs::create_dir(&dir)?;
468 dir
469 } else {
470 let mut gadget_idx: u16 = 0;
471 loop {
472 let dir = usb_gadget_dir.join(format!("usb-gadget{gadget_idx}"));
473 match fs::create_dir(&dir) {
474 Ok(()) => break dir,
475 Err(err) if err.kind() == ErrorKind::AlreadyExists => (),
476 Err(err) => return Err(err),
477 }
478 gadget_idx = gadget_idx
479 .checked_add(1)
480 .ok_or_else(|| Error::new(ErrorKind::OutOfMemory, "USB gadgets exhausted"))?;
481 }
482 };
483
484 log::debug!("registering gadget at {}", dir.display());
485
486 fs::write(dir.join("bDeviceClass"), hex_u8(self.device_class.class))?;
487 fs::write(dir.join("bDeviceSubClass"), hex_u8(self.device_class.sub_class))?;
488 fs::write(dir.join("bDeviceProtocol"), hex_u8(self.device_class.protocol))?;
489
490 fs::write(dir.join("idVendor"), hex_u16(self.id.vendor))?;
491 fs::write(dir.join("idProduct"), hex_u16(self.id.product))?;
492
493 fs::write(dir.join("bMaxPacketSize0"), hex_u8(self.max_packet_size0))?;
494 fs::write(dir.join("bcdDevice"), hex_u16(self.device_release))?;
495 fs::write(dir.join("bcdUSB"), hex_u16(self.usb_version.into()))?;
496
497 if let Some(v) = self.max_speed {
498 fs::write(dir.join("max_speed"), v.to_string())?;
499 }
500
501 if let Some(webusb) = &self.web_usb {
502 let webusb_dir = dir.join("webusb");
503 if webusb_dir.is_dir() {
504 fs::write(webusb_dir.join("bVendorCode"), hex_u8(webusb.vendor_code))?;
505 fs::write(webusb_dir.join("bcdVersion"), hex_u16(webusb.version.into()))?;
506 fs::write(webusb_dir.join("landingPage"), &webusb.landing_page)?;
507 fs::write(webusb_dir.join("use"), "1")?;
508 } else {
509 log::warn!("WebUSB descriptor is unsupported by kernel");
510 }
511 }
512
513 for (&lang, strs) in &self.strings {
514 let lang_dir = dir.join("strings").join(hex_u16(lang.into()));
515 fs::create_dir(&lang_dir)?;
516
517 fs::write(lang_dir.join("manufacturer"), &strs.manufacturer)?;
518 fs::write(lang_dir.join("product"), &strs.product)?;
519 fs::write(lang_dir.join("serialnumber"), &strs.serial_number)?;
520 }
521
522 let gadget_name = dir.file_name().unwrap().to_string_lossy();
523 let mut functions = Vec::new();
524 for func in self.configs.iter().flat_map(|c| &c.functions) {
525 if !functions.contains(&func) {
526 functions.push(func);
527 }
528 }
529 let mut func_dirs = HashMap::new();
530 for (func_idx, &func) in functions.iter().enumerate() {
531 let func_dir = dir.join(
532 dir.join("functions")
533 .join(format!("{}.{gadget_name}-{func_idx}", func.get().driver().to_string_lossy())),
534 );
535 log::debug!("creating function at {}", func_dir.display());
536 fs::create_dir(&func_dir)?;
537
538 func.get().dir().set_dir(&func_dir);
539 func.get().register()?;
540
541 func_dirs.insert(func.clone(), func_dir);
542 }
543
544 let mut config_dirs = Vec::new();
545 for (idx, config) in self.configs.iter().enumerate() {
546 let dir = config.register(&dir, idx + 1, &func_dirs)?;
547 config_dirs.push(dir);
548 }
549
550 if let Some(os_desc) = &self.os_descriptor {
551 let os_desc_dir = dir.join("os_desc");
552 if os_desc_dir.is_dir() {
553 fs::write(os_desc_dir.join("b_vendor_code"), hex_u8(os_desc.vendor_code))?;
554 fs::write(os_desc_dir.join("qw_sign"), &os_desc.qw_sign)?;
555 fs::write(os_desc_dir.join("use"), "1")?;
556
557 let config_dir = config_dirs.get(os_desc.config).ok_or_else(|| {
558 Error::new(ErrorKind::InvalidInput, "invalid configuration index in OS descriptor")
559 })?;
560 log::debug!("linking OS descriptor to config dir {}", config_dir.display());
561 symlink(config_dir, os_desc_dir.join(config_dir.file_name().unwrap()))?;
562 } else {
563 log::warn!("USB OS descriptor is unsupported by kernel");
564 }
565 }
566
567 log::debug!("gadget at {} registered", dir.display());
568 Ok(RegGadget { dir, attached: true, func_dirs })
569 }
570
571 #[must_use = "consumes the gadget"]
576 pub fn bind(self, udc: &Udc) -> Result<RegGadget> {
577 let reg = self.register()?;
578 reg.bind(Some(udc))?;
579 Ok(reg)
580 }
581}
582
583#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
587pub struct RegFunction {
588 driver: String,
590 instance: String,
592}
593
594impl RegFunction {
595 pub fn driver(&self) -> &str {
597 &self.driver
598 }
599
600 pub fn instance(&self) -> &str {
602 &self.instance
603 }
604}
605
606impl fmt::Display for RegFunction {
607 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
608 write!(f, "{}.{}", self.driver, self.instance)
609 }
610}
611
612#[must_use = "The USB gadget is removed when RegGadget is dropped."]
617pub struct RegGadget {
618 dir: PathBuf,
619 attached: bool,
620 func_dirs: HashMap<Handle, PathBuf>,
621}
622
623impl fmt::Debug for RegGadget {
624 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
625 f.debug_struct("RegGadget").field("name", &self.name()).field("is_attached", &self.is_attached()).finish()
626 }
627}
628
629impl RegGadget {
630 pub fn name(&self) -> &OsStr {
632 self.dir.file_name().unwrap()
633 }
634
635 pub fn path(&self) -> &Path {
637 &self.dir
638 }
639
640 pub fn is_attached(&self) -> bool {
642 self.attached
643 }
644
645 pub fn udc(&self) -> Result<Option<OsString>> {
647 let data = OsString::from_vec(fs::read(self.dir.join("UDC"))?);
648 let data = trim_os_str(&data);
649 if data.is_empty() {
650 Ok(None)
651 } else {
652 Ok(Some(data.to_os_string()))
653 }
654 }
655
656 pub fn functions(&self) -> Result<Vec<RegFunction>> {
658 let func_dir = self.dir.join("functions");
659 let mut functions = Vec::new();
660
661 if let Ok(entries) = fs::read_dir(&func_dir) {
662 for entry in entries {
663 let Ok(entry) = entry else { continue };
664 if !entry.metadata()?.is_dir() {
665 continue;
666 }
667 let name = entry.file_name().to_string_lossy().to_string();
668 if let Some((driver, instance)) = name.split_once('.') {
669 functions.push(RegFunction { driver: driver.to_string(), instance: instance.to_string() });
670 }
671 }
672 }
673
674 functions.sort();
675 Ok(functions)
676 }
677
678 pub fn bind(&self, udc: Option<&Udc>) -> Result<()> {
682 log::debug!("binding gadget {:?} to {:?}", self, &udc);
683
684 let name = match udc {
685 Some(udc) => udc.name().to_os_string(),
686 None => "\n".into(),
687 };
688
689 match fs::write(self.dir.join("UDC"), name.as_bytes()) {
690 Ok(()) => (),
691 Err(err) if udc.is_none() && err.raw_os_error() == Some(Errno::NODEV.raw_os_error()) => (),
692 Err(err) => return Err(err),
693 }
694
695 for func in self.func_dirs.keys() {
696 func.get().dir().set_bound(udc.is_some());
697 }
698
699 Ok(())
700 }
701
702 pub fn detach(&mut self) {
706 self.attached = false;
707 }
708
709 fn do_remove(&mut self) -> Result<()> {
710 for func in self.func_dirs.keys() {
711 func.get().pre_removal()?;
712 }
713
714 for func in self.func_dirs.keys() {
715 func.get().dir().set_bound(false);
716 }
717
718 remove_at(&self.dir)?;
719
720 for func in self.func_dirs.keys() {
721 func.get().dir().reset_dir();
722 }
723
724 for (func, dir) in &self.func_dirs {
725 func.get().post_removal(dir)?;
726 }
727
728 self.detach();
729 Ok(())
730 }
731
732 pub fn remove(mut self) -> Result<()> {
734 self.do_remove()
735 }
736}
737
738impl Drop for RegGadget {
739 fn drop(&mut self) {
740 if self.attached {
741 if let Err(err) = self.do_remove() {
742 log::warn!("removing gadget at {} failed: {err}", self.dir.display());
743 }
744 }
745 }
746}
747
748fn remove_at(dir: &Path) -> Result<()> {
750 log::debug!("removing gadget at {}", dir.display());
751
752 init_remove_handlers();
753
754 let _ = fs::write(dir.join("UDC"), "\n");
755
756 if let Ok(entries) = fs::read_dir(dir.join("os_desc")) {
757 for file in entries {
758 let Ok(file) = file else { continue };
759 let Ok(file_type) = file.file_type() else { continue };
760 if file_type.is_symlink() {
761 fs::remove_file(file.path())?;
762 }
763 }
764 }
765
766 for config_dir in fs::read_dir(dir.join("configs"))? {
767 let Ok(config_dir) = config_dir else { continue };
768 if !config_dir.metadata()?.is_dir() {
769 continue;
770 }
771
772 for func in fs::read_dir(config_dir.path())? {
773 let Ok(func) = func else { continue };
774 if func.metadata()?.is_symlink() {
775 fs::remove_file(func.path())?;
776 }
777 }
778
779 for lang in fs::read_dir(config_dir.path().join("strings"))? {
780 let Ok(lang) = lang else { continue };
781 if lang.metadata()?.is_dir() {
782 fs::remove_dir(lang.path())?;
783 }
784 }
785
786 fs::remove_dir(config_dir.path())?;
787 }
788
789 for func_dir in fs::read_dir(dir.join("functions"))? {
790 let Ok(func_dir) = func_dir else { continue };
791 if !func_dir.metadata()?.is_dir() {
792 continue;
793 }
794
795 call_remove_handler(&func_dir.path())?;
796
797 fs::remove_dir(func_dir.path())?;
798 }
799
800 for lang in fs::read_dir(dir.join("strings"))? {
801 let Ok(lang) = lang else { continue };
802 if lang.metadata()?.is_dir() {
803 fs::remove_dir(lang.path())?;
804 }
805 }
806
807 fs::remove_dir(dir)?;
808
809 log::debug!("removed gadget at {}", dir.display());
810 Ok(())
811}
812
813fn usb_gadget_dir() -> Result<PathBuf> {
815 let _ = request_module("libcomposite");
816
817 let usb_gadget_dir = configfs_dir()?.join("usb_gadget");
818 if usb_gadget_dir.is_dir() {
819 Ok(usb_gadget_dir)
820 } else {
821 Err(Error::new(ErrorKind::NotFound, "usb_gadget not found in configfs"))
822 }
823}
824
825pub fn registered() -> Result<Vec<RegGadget>> {
830 let usb_gadget_dir = usb_gadget_dir()?;
831
832 let mut gadgets = Vec::new();
833 for gadget_dir in fs::read_dir(usb_gadget_dir)? {
834 let Ok(gadget_dir) = gadget_dir else { continue };
835 if gadget_dir.metadata()?.is_dir() {
836 gadgets.push(RegGadget { dir: gadget_dir.path(), attached: false, func_dirs: HashMap::new() });
837 }
838 }
839
840 Ok(gadgets)
841}
842
843pub fn remove_all() -> Result<()> {
848 let mut res = Ok(());
849
850 for gadget in registered()? {
851 if let Err(err) = gadget.remove() {
852 res = Err(err);
853 }
854 }
855
856 res
857}
858
859pub fn unbind_all() -> Result<()> {
864 let mut res = Ok(());
865
866 for gadget in registered()? {
867 if let Err(err) = gadget.bind(None) {
868 res = Err(err);
869 }
870 }
871
872 res
873}