cadmus_core/device/usb/kobo/
mtk.rs1use crate::device::metadata::DeviceMetadata;
16use crate::device::usb::error::UsbError;
17use crate::device::usb::kobo::operations::KoboUsbOperations;
18use crate::device::usb::manager::UsbManager;
19use std::fs;
20use std::path::Path;
21use tracing::{debug, error, info, warn};
22
23const CONFIGFS_GADGET_DIR: &str = "/sys/kernel/config/usb_gadget/g1";
25
26const UDC_DIR: &str = "/sys/class/udc";
28
29fn discover_udc() -> Result<String, UsbError> {
39 let udc_path = Path::new(UDC_DIR);
40
41 if !udc_path.exists() {
42 warn!(path = UDC_DIR, "UDC directory does not exist");
43 return Err(UsbError::Udc("UDC directory not found".to_string()));
44 }
45
46 let mut entries = fs::read_dir(udc_path).map_err(|e| {
47 error!(path = UDC_DIR, error = %e, "Failed to read UDC directory");
48 UsbError::Udc(format!("Cannot read UDC directory: {}", e))
49 })?;
50
51 let entry = entries
52 .next()
53 .ok_or_else(|| UsbError::Udc("No UDC available".to_string()))?;
54 let entry = entry.map_err(|e| UsbError::Udc(format!("Cannot read UDC entry: {}", e)))?;
55 let name = entry
56 .file_name()
57 .into_string()
58 .map_err(|_| UsbError::Udc("UDC name contains invalid UTF-8".to_string()))?;
59
60 debug!(udc_name = %name, "Found UDC");
61 Ok(name)
62}
63
64pub struct MtkUsbManager {
70 metadata: DeviceMetadata,
71 udc: String,
72}
73
74impl MtkUsbManager {
75 #[cfg_attr(feature = "otel", tracing::instrument(skip(metadata)))]
85 pub fn new(metadata: DeviceMetadata) -> Result<Self, UsbError> {
86 let udc = discover_udc()?;
87 info!(
88 vendor_id = metadata.vendor_id,
89 product_id = metadata.product_id,
90 serial_number = %metadata.serial_number,
91 partition = %metadata.partition,
92 udc = %udc,
93 "MtkUsbManager constructed"
94 );
95 Ok(Self { metadata, udc })
96 }
97
98 fn create_gadget_dirs(&self) -> Result<(), UsbError> {
100 debug!("Creating ConfigFS gadget directories");
101
102 let dirs = [
103 format!("{}/strings/0x409", CONFIGFS_GADGET_DIR),
104 format!("{}/configs/c.1/strings/0x409", CONFIGFS_GADGET_DIR),
105 format!("{}/functions/mass_storage.0/lun.0", CONFIGFS_GADGET_DIR),
106 ];
107
108 for dir in &dirs {
109 fs::create_dir_all(dir).map_err(|e| {
110 error!(directory = %dir, error = %e, "Failed to create ConfigFS directory");
111 UsbError::GadgetConfig(format!("Cannot create directory {}: {}", dir, e))
112 })?;
113 }
114
115 Ok(())
116 }
117
118 fn write_gadget_config(&self) -> Result<(), UsbError> {
123 debug!("Writing gadget configuration to ConfigFS");
124
125 let base = Path::new(CONFIGFS_GADGET_DIR);
126
127 fs::write(
128 base.join("idVendor"),
129 format!("0x{:04X}\n", self.metadata.vendor_id),
130 )
131 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write idVendor: {}", e)))?;
132
133 fs::write(
134 base.join("idProduct"),
135 format!("0x{:04X}\n", self.metadata.product_id),
136 )
137 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write idProduct: {}", e)))?;
138
139 let strings = base.join("strings/0x409");
140 fs::write(
141 strings.join("serialnumber"),
142 format!("{}\n", self.metadata.serial_number),
143 )
144 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write serialnumber: {}", e)))?;
145
146 fs::write(
147 strings.join("manufacturer"),
148 format!("{}\n", self.metadata.manufacturer),
149 )
150 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write manufacturer: {}", e)))?;
151
152 fs::write(
153 strings.join("product"),
154 format!("{}\n", self.metadata.product),
155 )
156 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write product: {}", e)))?;
157
158 let config_strings = base.join("configs/c.1/strings/0x409");
159 fs::write(config_strings.join("configuration"), "KOBOeReader\n")
160 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write configuration: {}", e)))?;
161
162 let lun = base.join("functions/mass_storage.0/lun.0");
163 fs::write(lun.join("file"), format!("{}\n", self.metadata.partition))
164 .map_err(|e| UsbError::GadgetConfig(format!("Cannot write LUN file: {}", e)))?;
165
166 info!(
167 vendor_id = self.metadata.vendor_id,
168 product_id = self.metadata.product_id,
169 partition = %self.metadata.partition,
170 "Gadget configuration written"
171 );
172
173 Ok(())
174 }
175
176 fn activate_function(&self) -> Result<(), UsbError> {
178 debug!("Activating mass storage function");
179
180 let src = format!("{}/functions/mass_storage.0", CONFIGFS_GADGET_DIR);
181 let dst = format!("{}/configs/c.1/mass_storage.0", CONFIGFS_GADGET_DIR);
182
183 std::os::unix::fs::symlink(&src, &dst).map_err(|e| {
184 error!(source = %src, destination = %dst, error = %e, "Failed to create function symlink");
185 UsbError::GadgetConfig(format!("Cannot activate function: {}", e))
186 })?;
187
188 Ok(())
189 }
190
191 fn bind_udc(&self) -> Result<(), UsbError> {
196 debug!(udc = %self.udc, "Binding to UDC");
197
198 let udc_path = format!("{}/UDC", CONFIGFS_GADGET_DIR);
199 fs::write(&udc_path, format!("{}\n", self.udc)).map_err(|e| {
200 error!(udc = %self.udc, error = %e, "Failed to bind UDC");
201 UsbError::Udc(format!("Cannot bind UDC: {}", e))
202 })?;
203
204 info!(udc = %self.udc, "USB gadget enabled");
205 Ok(())
206 }
207
208 fn unbind_udc(&self) -> Result<(), UsbError> {
213 debug!("Unbinding UDC");
214
215 let udc_path = format!("{}/UDC", CONFIGFS_GADGET_DIR);
216 fs::write(&udc_path, "\n").map_err(|e| {
217 error!(error = %e, "Failed to unbind UDC");
218 UsbError::Udc(format!("Cannot unbind UDC: {}", e))
219 })?;
220
221 info!("USB gadget disabled");
222 Ok(())
223 }
224
225 fn deactivate_function(&self) -> Result<(), UsbError> {
227 debug!("Deactivating mass storage function");
228
229 let link = format!("{}/configs/c.1/mass_storage.0", CONFIGFS_GADGET_DIR);
230 if Path::new(&link).exists() {
231 fs::remove_file(&link).map_err(|e| {
232 error!(path = %link, error = %e, "Failed to remove function symlink");
233 UsbError::GadgetConfig(format!("Cannot deactivate function: {}", e))
234 })?;
235 }
236
237 Ok(())
238 }
239
240 fn remove_gadget_dirs(&self) -> Result<(), UsbError> {
242 debug!("Removing ConfigFS gadget directories");
243
244 let dirs = [
245 format!("{}/configs/c.1/strings/0x409", CONFIGFS_GADGET_DIR),
246 format!("{}/configs/c.1", CONFIGFS_GADGET_DIR),
247 format!("{}/functions/mass_storage.0/lun.0", CONFIGFS_GADGET_DIR),
248 format!("{}/functions/mass_storage.0", CONFIGFS_GADGET_DIR),
249 format!("{}/strings/0x409", CONFIGFS_GADGET_DIR),
250 CONFIGFS_GADGET_DIR.to_string(),
251 ];
252
253 for dir in &dirs {
254 if Path::new(dir).exists() {
255 if let Err(e) = fs::remove_dir(dir) {
256 debug!(directory = %dir, error = %e, "Failed to remove directory (may be non-empty)");
257 }
258 }
259 }
260
261 Ok(())
262 }
263}
264
265impl KoboUsbOperations for MtkUsbManager {
266 fn metadata(&self) -> &DeviceMetadata {
267 &self.metadata
268 }
269}
270
271impl UsbManager for MtkUsbManager {
272 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
273 fn enable(&self) -> Result<(), UsbError> {
274 info!("Enabling MTK USB mass storage");
275
276 self.prepare_for_usb_share()?;
277 self.create_gadget_dirs()?;
278 self.write_gadget_config()?;
279 self.activate_function()?;
280 self.bind_udc()?;
281
282 info!("MTK USB mass storage enabled successfully");
283 Ok(())
284 }
285
286 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
287 fn disable(&self) -> Result<(), UsbError> {
288 info!("Disabling MTK USB mass storage");
289
290 self.unbind_udc()?;
291 self.deactivate_function()?;
292 self.remove_gadget_dirs()?;
293 self.check_filesystem()?;
294 self.remount_partitions()?;
295
296 info!("MTK USB mass storage disabled successfully");
297 Ok(())
298 }
299}