cadmus_core/device/usb/kobo/
legacy.rs1use crate::device::metadata::{DeviceMetadata, Platform};
9use crate::device::usb::error::UsbError;
10use crate::device::usb::kobo::operations::KoboUsbOperations;
11use crate::device::usb::manager::UsbManager;
12use std::fs;
13use std::path::Path;
14use std::process::Command;
15use tracing::{debug, error, info, warn};
16
17const DRIVERS_DIR: &str = "/drivers";
19
20const SD_CARD_PARTITION: &str = "/dev/mmcblk1p1";
22
23pub struct LegacyUsbManager {
34 metadata: DeviceMetadata,
35 platform: Platform,
36}
37
38impl LegacyUsbManager {
39 pub fn new(metadata: DeviceMetadata, platform: Platform) -> Self {
44 Self { metadata, platform }
45 }
46
47 fn drivers_path(&self) -> String {
48 format!("{}/{}", DRIVERS_DIR, self.platform)
49 }
50
51 fn has_g_mass_storage(&self) -> bool {
52 let path = format!("{}/g_mass_storage.ko", self.drivers_path());
53 Path::new(&path).exists()
54 }
55
56 fn build_file_param(&self) -> String {
59 if Path::new(SD_CARD_PARTITION).exists() {
60 debug!(
61 sd_partition = SD_CARD_PARTITION,
62 "SD card detected, including in USB export"
63 );
64 format!("{},{}", self.metadata.partition, SD_CARD_PARTITION)
65 } else {
66 self.metadata.partition.clone()
67 }
68 }
69
70 fn build_mass_storage_params(&self) -> Vec<String> {
71 vec![
72 format!("idVendor=0x{:04X}", self.metadata.vendor_id),
73 format!("idProduct=0x{:04X}", self.metadata.product_id),
74 "iManufacturer=Kobo".to_string(),
75 format!("iProduct=eReader-{}", self.metadata.firmware_version),
76 format!("iSerialNumber={}", self.metadata.serial_number),
77 format!("file={}", self.build_file_param()),
78 "stall=1".to_string(),
79 "removable=1".to_string(),
80 ]
81 }
82
83 fn build_file_storage_params(&self) -> Vec<String> {
84 match self.platform {
85 Platform::MX6SLLNTX | Platform::MX6ULLNTX => self.build_mass_storage_params(),
86 _ => {
87 vec![
88 format!("vendor=0x{:04X}", self.metadata.vendor_id),
89 format!("product=0x{:04X}", self.metadata.product_id),
90 "vendor_id=Kobo".to_string(),
91 format!("product_id=eReader-{}", self.metadata.firmware_version),
92 format!("SN={}", self.metadata.serial_number),
93 format!("file={}", self.build_file_param()),
94 "stall=1".to_string(),
95 "removable=1".to_string(),
96 ]
97 }
98 }
99 }
100
101 fn load_g_mass_storage(&self) -> Result<(), UsbError> {
102 info!("Loading g_mass_storage module");
103
104 let module_path = format!("{}/g_mass_storage.ko", self.drivers_path());
105 let params = self.build_mass_storage_params();
106
107 let mut cmd = Command::new("insmod");
108 cmd.arg(&module_path);
109 for param in ¶ms {
110 cmd.arg(param);
111 }
112
113 let output = cmd.output().map_err(|e| {
114 error!(error = %e, "Failed to execute insmod");
115 UsbError::KernelModule(format!("insmod execution failed: {}", e))
116 })?;
117
118 if !output.status.success() {
119 let stderr = String::from_utf8_lossy(&output.stderr);
120 error!(stderr = %stderr, "insmod failed");
121 return Err(UsbError::KernelModule(format!(
122 "Failed to load g_mass_storage: {}",
123 stderr
124 )));
125 }
126
127 info!("g_mass_storage module loaded successfully");
128 Ok(())
129 }
130
131 fn load_g_file_storage(&self) -> Result<(), UsbError> {
132 info!("Loading g_file_storage module with dependencies");
133
134 let gadgets_path = format!("{}/{}/usb/gadget", DRIVERS_DIR, self.platform);
135
136 match self.platform {
137 Platform::MX6SLLNTX | Platform::MX6ULLNTX => {
138 for module in ["configfs.ko", "libcomposite.ko", "usb_f_mass_storage.ko"] {
139 let path = format!("{}/{}", gadgets_path, module);
140 if Path::new(&path).exists() {
141 debug!(module = %module, "Loading dependency module");
142 let _ = Command::new("insmod").arg(&path).output();
143 }
144 }
145 }
146 Platform::MX6SLNTX => {}
147 _ => {
148 let arcotg_path = format!("{}/arcotg_udc.ko", gadgets_path);
149 if Path::new(&arcotg_path).exists() {
150 debug!("Loading arcotg_udc module");
151 let _ = Command::new("insmod").arg(&arcotg_path).output();
152 std::thread::sleep(std::time::Duration::from_secs(2));
153 }
154 }
155 }
156
157 let module_path = format!("{}/g_file_storage.ko", gadgets_path);
158 let params = self.build_file_storage_params();
159
160 let mut cmd = Command::new("insmod");
161 cmd.arg(&module_path);
162 for param in ¶ms {
163 cmd.arg(param);
164 }
165
166 let output = cmd.output().map_err(|e| {
167 error!(error = %e, "Failed to execute insmod");
168 UsbError::KernelModule(format!("insmod execution failed: {}", e))
169 })?;
170
171 if !output.status.success() {
172 let stderr = String::from_utf8_lossy(&output.stderr);
173 error!(stderr = %stderr, "insmod failed");
174 return Err(UsbError::KernelModule(format!(
175 "Failed to load g_file_storage: {}",
176 stderr
177 )));
178 }
179
180 info!("g_file_storage module loaded successfully");
181 Ok(())
182 }
183
184 fn load_usb_module(&self) -> Result<(), UsbError> {
185 if self.has_g_mass_storage() {
186 self.load_g_mass_storage()
187 } else {
188 self.load_g_file_storage()
189 }
190 }
191
192 fn get_loaded_module(&self) -> Option<String> {
193 let content = fs::read_to_string("/proc/modules").ok()?;
194
195 for line in content.lines() {
196 if line.starts_with("g_mass_storage ") {
197 return Some("g_mass_storage".to_string());
198 }
199 if line.starts_with("g_file_storage ") {
200 return Some("g_file_storage".to_string());
201 }
202 }
203
204 None
205 }
206
207 fn unload_usb_modules(&self) -> Result<(), UsbError> {
208 let module = match self.get_loaded_module() {
209 Some(m) => m,
210 None => {
211 warn!("No USB module found in /proc/modules");
212 return Ok(());
213 }
214 };
215
216 info!(module = %module, "Unloading USB module");
217
218 let output = Command::new("rmmod").arg(&module).output().map_err(|e| {
219 error!(error = %e, "Failed to execute rmmod");
220 UsbError::KernelModule(format!("rmmod execution failed: {}", e))
221 })?;
222
223 if !output.status.success() {
224 let stderr = String::from_utf8_lossy(&output.stderr);
225 error!(stderr = %stderr, "rmmod failed");
226 return Err(UsbError::KernelModule(format!(
227 "Failed to unload {}: {}",
228 module, stderr
229 )));
230 }
231
232 if module == "g_file_storage" {
233 match self.platform {
234 Platform::MX6SLLNTX | Platform::MX6ULLNTX => {
235 for mod_name in ["usb_f_mass_storage", "libcomposite", "configfs"] {
236 let _ = Command::new("rmmod").arg(mod_name).output();
237 }
238 }
239 Platform::MX6SLNTX => {}
240 _ => {
241 let _ = Command::new("rmmod").arg("arcotg_udc").output();
242 }
243 }
244 }
245
246 info!("USB modules unloaded successfully");
247 Ok(())
248 }
249}
250
251impl KoboUsbOperations for LegacyUsbManager {
252 fn metadata(&self) -> &DeviceMetadata {
253 &self.metadata
254 }
255}
256
257impl UsbManager for LegacyUsbManager {
258 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
259 fn enable(&self) -> Result<(), UsbError> {
260 info!(platform = %self.platform, "Enabling legacy USB mass storage");
261
262 self.prepare_for_usb_share()?;
263 self.load_usb_module()?;
264
265 std::thread::sleep(std::time::Duration::from_secs(1));
266
267 info!("Legacy USB mass storage enabled successfully");
268 Ok(())
269 }
270
271 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
272 fn disable(&self) -> Result<(), UsbError> {
273 info!(platform = %self.platform, "Disabling legacy USB mass storage");
274
275 self.unload_usb_modules()?;
276
277 std::thread::sleep(std::time::Duration::from_secs(1));
278
279 self.check_filesystem()?;
280 self.remount_partitions()?;
281
282 info!("Legacy USB mass storage disabled successfully");
283 Ok(())
284 }
285}