1mod types;
70
71#[cfg(target_os = "linux")]
72use procfs;
73
74use crate::device::wifi::error::WifiError;
75use crate::device::wifi::kobo::types::{PowerToggle, WifiModule, WifiModuleConfig};
76use crate::device::wifi::manager::WifiManager;
77use nix::ioctl_write_int_bad;
78use std::fs;
79use std::os::fd::AsRawFd;
80use std::path::Path;
81use std::process::Command;
82use std::sync::Mutex;
83use tracing::{debug, error, info, warn};
84
85const DRIVERS_DIR: &str = "/drivers";
86const NTX_IO_PATH: &str = "/dev/ntx_io";
87const WMT_WIFI_PATH: &str = "/dev/wmtWifi";
88const CONFIG_PATH: &str = "/mnt/onboard/.kobo/Kobo/Kobo eReader.conf";
89const WPA_SUPPLICANT_CONF: &str = "/etc/wpa_supplicant/wpa_supplicant.conf";
90const WPA_SUPPLICANT_SOCKET: &str = "/var/run/wpa_supplicant";
91const WIFI_POST_UP_SCRIPT: &str = "scripts/wifi-post-up.sh";
92const WIFI_POST_DOWN_SCRIPT: &str = "scripts/wifi-post-down.sh";
93const WIFI_PRE_UP_SCRIPT: &str = "scripts/wifi-pre-up.sh";
94const WIFI_PRE_DOWN_SCRIPT: &str = "scripts/wifi-pre-down.sh";
95
96const NTX_IO_WIFI_CTRL: u8 = 208;
97ioctl_write_int_bad!(set_ntx_io_wifi_ctrl, NTX_IO_WIFI_CTRL as libc::c_int);
98
99#[cfg(target_os = "linux")]
100#[cfg_attr(feature = "otel", tracing::instrument(skip_all, ret(level=tracing::Level::TRACE)))]
101fn is_module_loaded(module_name: &str) -> bool {
102 procfs::modules()
103 .map(|modules| modules.iter().any(|(name, _)| name == module_name))
104 .unwrap_or(false)
105}
106
107#[cfg(not(target_os = "linux"))]
108#[cfg_attr(feature = "otel", tracing::instrument(skip_all, ret(level=tracing::Level::TRACE)))]
109fn is_module_loaded(_module_name: &str) -> bool {
110 unreachable!("is_module_loaded is only implemented on Linux")
111}
112
113#[cfg(target_os = "linux")]
114fn is_interface_up(interface: &str) -> bool {
115 let operstate_path = format!("/sys/class/net/{}/operstate", interface);
116 if let Ok(state) = fs::read_to_string(&operstate_path) {
117 let state = state.trim();
118 return state == "up" || state == "unknown";
119 }
120 false
121}
122
123#[cfg(not(target_os = "linux"))]
124fn is_interface_up(_interface: &str) -> bool {
125 unreachable!("is_interface_up is only implemented on Linux")
126}
127
128pub struct KoboWifiManager {
129 config: WifiModuleConfig,
130 lock: Mutex<()>,
131}
132
133impl KoboWifiManager {
134 pub fn new(config: WifiModuleConfig) -> Self {
135 Self {
136 config,
137 lock: Mutex::new(()),
138 }
139 }
140
141 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
142 fn run_script(&self, script: &str) {
143 if Path::new(script).exists() {
144 let output = Command::new(script).output();
145 if let Ok(output) = output {
146 if !output.status.success() {
147 warn!(
148 script,
149 stderr = %String::from_utf8_lossy(&output.stderr),
150 "WiFi script failed"
151 );
152 } else {
153 debug!(script, "WiFi script succeeded");
154 }
155 }
156 }
157 }
158
159 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(path = %path), ret(level=tracing::Level::TRACE)))]
160 fn insmod(&self, path: &str) -> Result<(), WifiError> {
161 let output = Command::new("insmod").arg(path).output().map_err(|e| {
162 error!(error = %e, path, "Failed to execute insmod");
163 WifiError::KernelModule(format!("insmod execution failed: {}", e))
164 })?;
165
166 if !output.status.success() {
167 let stderr = String::from_utf8_lossy(&output.stderr);
168 error!(path, stderr = %stderr, "insmod failed");
169 return Err(WifiError::KernelModule(format!(
170 "Failed to load module {}: {}",
171 path, stderr
172 )));
173 }
174
175 debug!(path, "Module loaded successfully");
176 Ok(())
177 }
178
179 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(path = %path, module_name = %module_name), ret(level=tracing::Level::TRACE)))]
184 fn insmod_asneeded(&self, path: &str, module_name: &str) -> Result<(), WifiError> {
185 if !is_module_loaded(module_name) {
186 match self.insmod(path) {
187 Ok(()) => {
188 std::thread::sleep(std::time::Duration::from_millis(250));
189 }
190 Err(WifiError::KernelModule(ref msg)) if msg.contains("File exists") => {
191 debug!(
192 module_name,
193 "Module already loaded (insmod returned File exists)"
194 );
195 }
196 Err(e) => return Err(e),
197 }
198 } else {
199 debug!(module_name, "Module already loaded");
200 }
201 Ok(())
202 }
203
204 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(path = %path, module_name = %module_name), ret(level=tracing::Level::TRACE)))]
209 fn insmod_asneeded_with_params(
210 &self,
211 path: &str,
212 module_name: &str,
213 params: &[&str],
214 ) -> Result<(), WifiError> {
215 if !is_module_loaded(module_name) {
216 let output = Command::new("insmod")
217 .arg(path)
218 .args(params)
219 .output()
220 .map_err(|e| {
221 error!(error = %e, path, "Failed to execute insmod");
222 WifiError::KernelModule(format!("insmod execution failed: {}", e))
223 })?;
224
225 if !output.status.success() {
226 let stderr = String::from_utf8_lossy(&output.stderr);
227 if stderr.contains("File exists") {
228 debug!(
229 module_name,
230 "Module already loaded (insmod returned File exists)"
231 );
232 } else {
233 error!(path, stderr = %stderr, "insmod failed");
234 return Err(WifiError::KernelModule(format!(
235 "Failed to load module {}: {}",
236 path, stderr
237 )));
238 }
239 } else {
240 std::thread::sleep(std::time::Duration::from_millis(250));
241 }
242 } else {
243 debug!(module_name, "Module already loaded");
244 }
245 Ok(())
246 }
247
248 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(module_name = %module_name), ret(level=tracing::Level::TRACE)))]
249 fn rmmod(&self, module_name: &str) -> Result<(), WifiError> {
250 if !is_module_loaded(module_name) {
251 debug!(module_name, "Module not loaded, skipping rmmod");
252 return Ok(());
253 }
254
255 let output = Command::new("rmmod")
256 .arg(module_name)
257 .output()
258 .map_err(|e| {
259 error!(error = %e, module_name, "Failed to execute rmmod");
260 WifiError::KernelModule(format!("rmmod execution failed: {}", e))
261 })?;
262
263 if !output.status.success() {
264 let stderr = String::from_utf8_lossy(&output.stderr);
265 error!(module_name, stderr = %stderr, "rmmod failed");
266 return Err(WifiError::KernelModule(format!(
267 "Failed to unload {}: {}",
268 module_name, stderr
269 )));
270 }
271
272 debug!(module_name, "Module unloaded successfully");
273 Ok(())
274 }
275
276 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
277 fn power_up_ntx_io(&self) -> Result<(), WifiError> {
278 use std::os::unix::fs::OpenOptionsExt;
279
280 let fd = std::fs::OpenOptions::new()
281 .write(true)
282 .custom_flags(libc::O_NONBLOCK)
283 .open(NTX_IO_PATH)
284 .map_err(|e| {
285 error!(error = %e, "Failed to open ntx_io");
286 WifiError::Ioctl(format!("Failed to open {}: {}", NTX_IO_PATH, e))
287 })?;
288
289 self.ioctl_wifi_ctrl(fd.as_raw_fd(), 1)?;
290
291 info!("WiFi powered up via ntx_io");
292 Ok(())
293 }
294
295 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
296 fn power_down_ntx_io(&self) -> Result<(), WifiError> {
297 use std::os::unix::fs::OpenOptionsExt;
298
299 let fd = std::fs::OpenOptions::new()
300 .write(true)
301 .custom_flags(libc::O_NONBLOCK)
302 .open(NTX_IO_PATH)
303 .map_err(|e| {
304 error!(error = %e, "Failed to open ntx_io");
305 WifiError::Ioctl(format!("Failed to open {}: {}", NTX_IO_PATH, e))
306 })?;
307
308 self.ioctl_wifi_ctrl(fd.as_raw_fd(), 0)?;
309
310 info!("WiFi powered down via ntx_io");
311 Ok(())
312 }
313
314 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(enable = enable), ret(level=tracing::Level::TRACE)))]
315 fn ioctl_wifi_ctrl(&self, fd: std::os::fd::RawFd, enable: u8) -> Result<(), WifiError> {
316 let ret = unsafe { set_ntx_io_wifi_ctrl(fd, enable as libc::c_int) }.map_err(|e| {
317 WifiError::Ioctl(format!(
318 "ioctl CM_WIFI_CTRL with arg {} failed: {}",
319 enable, e
320 ))
321 })?;
322
323 if ret < 0 {
324 return Err(WifiError::Ioctl(format!(
325 "ioctl CM_WIFI_CTRL with arg {} failed",
326 enable
327 )));
328 }
329
330 debug!(enable, "ioctl CM_WIFI_CTRL succeeded");
331 Ok(())
332 }
333
334 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
335 fn power_up_wmt(&self) -> Result<(), WifiError> {
336 let module_path = &self.config.module_path;
337
338 self.insmod_asneeded(&format!("{}/wmt_drv.ko", module_path), "wmt_drv")?;
339 self.insmod_asneeded(
340 &format!("{}/wmt_chrdev_wifi.ko", module_path),
341 "wmt_chrdev_wifi",
342 )?;
343 self.insmod_asneeded(&format!("{}/wmt_cdev_bt.ko", module_path), "wmt_cdev_bt")?;
344
345 let wifi_module_path = format!("{}/{}.ko", module_path, self.config.module);
346 if Path::new(&wifi_module_path).exists() {
347 self.insmod_asneeded(&wifi_module_path, self.config.module.as_ref())?;
348 }
349
350 fs::write("/proc/driver/wmt_dbg", "0xDB9DB9").ok();
351 fs::write("/proc/driver/wmt_dbg", "7 9 0").ok();
352 std::thread::sleep(std::time::Duration::from_secs(1));
353 fs::write("/proc/driver/wmt_dbg", "0xDB9DB9").ok();
354 fs::write("/proc/driver/wmt_dbg", "7 9 1").ok();
355
356 fs::write(WMT_WIFI_PATH, "1").map_err(|e| {
357 error!(error = %e, "Failed to write to wmtWifi");
358 WifiError::Ioctl(format!("Failed to write to {}: {}", WMT_WIFI_PATH, e))
359 })?;
360
361 info!("WiFi powered up via WMT");
362 Ok(())
363 }
364
365 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
366 fn power_down_wmt(&self) -> Result<(), WifiError> {
367 if !Path::new(WMT_WIFI_PATH).exists() {
368 debug!("wmtWifi not present, skipping power down");
369 return Ok(());
370 }
371 match fs::write(WMT_WIFI_PATH, "0") {
372 Ok(()) => {
373 info!("WiFi powered down via WMT");
374 Ok(())
375 }
376 Err(e) => {
377 debug!(error = %e, "wmtWifi power down failed, assuming already off");
378 Ok(())
379 }
380 }
381 }
382
383 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
384 fn power_up_module(&self) -> Result<(), WifiError> {
385 let module_path = &self.config.module_path;
386 self.insmod_asneeded(
387 &format!("{}/sdio_wifi_pwr.ko", module_path),
388 "sdio_wifi_pwr",
389 )?;
390 info!("WiFi powered up via module");
391 Ok(())
392 }
393
394 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
395 fn power_down_module(&self) -> Result<(), WifiError> {
396 self.rmmod("sdio_wifi_pwr")?;
397 info!("WiFi powered down via module");
398 Ok(())
399 }
400
401 #[cfg_attr(feature = "otel", tracing::instrument(skip_all, ret(level=tracing::Level::TRACE)))]
402 fn read_country_code(&self) -> Option<String> {
403 let content = fs::read_to_string(CONFIG_PATH).ok()?;
404 for line in content.lines() {
405 if line.starts_with("WifiRegulatoryDomain=") {
406 return Some(line.split('=').nth(1)?.to_string());
407 }
408 }
409 None
410 }
411
412 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
413 fn build_module_params(&self) -> Vec<String> {
414 let mut params = Vec::new();
415
416 if let Some(country_code) = self.read_country_code() {
417 match &self.config.module {
418 WifiModule::Eight821cs => {
419 params.push(format!("rtw_country_code={}", country_code));
420 }
421 WifiModule::Moal => {
422 params.push(format!("reg_alpha2={}", country_code));
423 }
424 _ => {}
425 }
426 }
427
428 if self.config.module == WifiModule::Moal {
429 params.push("mod_para=nxp/wifi_mod_para_sd8987.conf".to_string());
430 }
431
432 params
433 }
434
435 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
436 fn load_wifi_module(&self) -> Result<(), WifiError> {
437 let module_params = self.build_module_params();
438 let platform = std::env::var("PLATFORM").unwrap_or_default();
439
440 if self.config.module == WifiModule::Moal {
441 let mlan_path = if Path::new(&format!("{}/{}/mlan.ko", DRIVERS_DIR, platform)).exists()
442 {
443 format!("{}/{}/mlan.ko", DRIVERS_DIR, platform)
444 } else {
445 format!("{}/mlan.ko", self.config.module_path)
446 };
447
448 if Path::new(&mlan_path).exists() && !is_module_loaded("mlan") {
449 self.insmod(&mlan_path)?;
450 }
451 }
452
453 let wifi_module_path = if Path::new(&format!(
454 "{}/{}/{}.ko",
455 DRIVERS_DIR, platform, self.config.module
456 ))
457 .exists()
458 {
459 format!("{}/{}/{}.ko", DRIVERS_DIR, platform, self.config.module)
460 } else {
461 format!("{}/{}.ko", self.config.module_path, self.config.module)
462 };
463
464 if !Path::new(&wifi_module_path).exists() {
465 return Err(WifiError::KernelModule(format!(
466 "WiFi module not found: {}",
467 wifi_module_path
468 )));
469 }
470
471 let params: Vec<&str> = module_params.iter().map(|s| s.as_str()).collect();
472 self.insmod_asneeded_with_params(&wifi_module_path, self.config.module.as_ref(), ¶ms)?;
473
474 debug!(
475 module = %self.config.module,
476 "WiFi module loaded"
477 );
478 Ok(())
479 }
480
481 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(interface = %self.config.interface), ret(level=tracing::Level::TRACE)))]
482 fn wait_for_interface(&self) -> Result<(), WifiError> {
483 let interface_path = format!("/sys/class/net/{}", self.config.interface);
484 let max_attempts = 20;
485
486 for attempt in 0..max_attempts {
487 if Path::new(&interface_path).exists() {
488 debug!(
489 interface = %self.config.interface,
490 attempt,
491 "Network interface appeared"
492 );
493 return Ok(());
494 }
495 std::thread::sleep(std::time::Duration::from_millis(200));
496 }
497
498 Err(WifiError::Interface(format!(
499 "Network interface {} did not appear after {} attempts",
500 self.config.interface, max_attempts
501 )))
502 }
503
504 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
505 fn start_wpa_supplicant(&self) -> Result<(), WifiError> {
506 use std::process::Command;
507
508 if self.is_wpa_supplicant_running() {
509 debug!("wpa_supplicant already running");
510 return Ok(());
511 }
512
513 let output = Command::new("wpa_supplicant")
514 .arg("-D")
515 .arg(self.config.wpa_supplicant_driver)
516 .arg("-s")
517 .arg("-i")
518 .arg(&self.config.interface)
519 .arg("-c")
520 .arg(WPA_SUPPLICANT_CONF)
521 .arg("-C")
522 .arg(WPA_SUPPLICANT_SOCKET)
523 .arg("-B")
524 .env("LD_LIBRARY_PATH", "")
525 .output()
526 .map_err(|e| {
527 error!(error = %e, "Failed to execute wpa_supplicant");
528 WifiError::Interface(format!("Failed to start wpa_supplicant: {}", e))
529 })?;
530
531 if !output.status.success() {
532 let stderr = String::from_utf8_lossy(&output.stderr);
533 warn!(
534 stderr = %stderr,
535 "wpa_supplicant may have failed, continuing"
536 );
537 } else {
538 debug!("wpa_supplicant started");
539 }
540
541 Ok(())
542 }
543
544 #[cfg_attr(feature = "otel", tracing::instrument(skip_all, ret(level=tracing::Level::TRACE)))]
545 fn is_wpa_supplicant_running(&self) -> bool {
546 std::process::Command::new("pkill")
547 .args(["-0", "wpa_supplicant"])
548 .output()
549 .map(|o| o.status.success())
550 .unwrap_or(false)
551 }
552
553 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
554 fn stop_wpa_supplicant(&self) -> Result<(), WifiError> {
555 let output = std::process::Command::new("wpa_cli")
556 .arg("-i")
557 .arg(&self.config.interface)
558 .arg("terminate")
559 .output()
560 .map_err(|e| {
561 error!(error = %e, "Failed to execute wpa_cli");
562 WifiError::Interface(format!("Failed to stop wpa_supplicant: {}", e))
563 })?;
564
565 if !output.status.success() {
566 let stderr = String::from_utf8_lossy(&output.stderr);
567 debug!(stderr = %stderr, "wpa_cli terminate may have failed");
568 } else {
569 debug!("wpa_supplicant stopped");
570 }
571
572 Ok(())
573 }
574
575 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(interface = %self.config.interface), ret(level=tracing::Level::TRACE)))]
576 fn ifconfig_up(&self) -> Result<(), WifiError> {
577 let output = std::process::Command::new("ifconfig")
578 .arg(&self.config.interface)
579 .arg("up")
580 .output()
581 .map_err(|e| {
582 error!(error = %e, "Failed to execute ifconfig");
583 WifiError::Interface(format!("Failed to bring up interface: {}", e))
584 })?;
585
586 if !output.status.success() {
587 let stderr = String::from_utf8_lossy(&output.stderr);
588 error!(stderr = %stderr, "ifconfig up failed");
589 return Err(WifiError::Interface(format!(
590 "Failed to bring up interface: {}",
591 stderr
592 )));
593 }
594
595 debug!(interface = %self.config.interface, "Interface brought up");
596 Ok(())
597 }
598
599 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(interface = %self.config.interface), ret(level=tracing::Level::TRACE)))]
600 fn ifconfig_down(&self) -> Result<(), WifiError> {
601 let output = std::process::Command::new("ifconfig")
602 .arg(&self.config.interface)
603 .arg("down")
604 .output()
605 .map_err(|e| {
606 error!(error = %e, "Failed to execute ifconfig");
607 WifiError::Interface(format!("Failed to bring down interface: {}", e))
608 })?;
609
610 if !output.status.success() {
611 let stderr = String::from_utf8_lossy(&output.stderr);
612 debug!(stderr = %stderr, "ifconfig down may have failed");
613 } else {
614 debug!(interface = %self.config.interface, "Interface brought down");
615 }
616
617 Ok(())
618 }
619
620 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(interface = %self.config.interface), ret(level=tracing::Level::TRACE)))]
621 fn wlarm_le_up(&self) -> Result<(), WifiError> {
622 if self.config.module != WifiModule::Dhd {
623 return Ok(());
624 }
625
626 if let Err(e) = std::process::Command::new("wlarm_le")
627 .arg("-i")
628 .arg(&self.config.interface)
629 .arg("up")
630 .output()
631 {
632 warn!(error = %e, "Failed to execute wlarm_le up");
633 return Ok(());
634 }
635
636 debug!(interface = %self.config.interface, "wlarm_le up succeeded");
637 Ok(())
638 }
639
640 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), fields(interface = %self.config.interface), ret(level=tracing::Level::TRACE)))]
641 fn wlarm_le_down(&self) -> Result<(), WifiError> {
642 if self.config.module != WifiModule::Dhd {
643 return Ok(());
644 }
645
646 if let Err(e) = std::process::Command::new("wlarm_le")
647 .arg("-i")
648 .arg(&self.config.interface)
649 .arg("down")
650 .output()
651 {
652 warn!(error = %e, "Failed to execute wlarm_le down");
653 return Ok(());
654 }
655
656 debug!(interface = %self.config.interface, "wlarm_le down succeeded");
657 Ok(())
658 }
659
660 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
661 fn power_up(&self) -> Result<(), WifiError> {
662 match self.config.power_toggle {
663 PowerToggle::Wmt => self.power_up_wmt()?,
664 PowerToggle::NtxIo => self.power_up_ntx_io()?,
665 PowerToggle::Module => self.power_up_module()?,
666 }
667 Ok(())
668 }
669
670 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
671 fn power_down(&self) -> Result<(), WifiError> {
672 match self.config.power_toggle {
673 PowerToggle::Wmt => self.power_down_wmt()?,
674 PowerToggle::NtxIo => self.power_down_ntx_io()?,
675 PowerToggle::Module => self.power_down_module()?,
676 }
677 Ok(())
678 }
679}
680
681impl WifiManager for KoboWifiManager {
682 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
683 fn enable(&self) -> Result<(), WifiError> {
684 let _lock = self
685 .lock
686 .lock()
687 .map_err(|e| WifiError::Lock(format!("Failed to acquire lock: {}", e)))?;
688
689 if is_module_loaded(self.config.module.as_ref()) && is_interface_up(&self.config.interface)
690 {
691 info!("WiFi already enabled, skipping");
692 return Ok(());
693 }
694
695 info!(
696 module = %self.config.module,
697 interface = %self.config.interface,
698 "Enabling WiFi"
699 );
700
701 self.run_script(WIFI_PRE_UP_SCRIPT);
702
703 self.power_up()?;
704 self.load_wifi_module()?;
705 self.wait_for_interface()?;
706 self.ifconfig_up()?;
707 self.wlarm_le_up()?;
708 self.start_wpa_supplicant()?;
709
710 self.run_script(WIFI_POST_UP_SCRIPT);
711
712 info!("WiFi enabled successfully");
713 Ok(())
714 }
715
716 #[cfg_attr(feature = "otel", tracing::instrument(skip(self), ret(level=tracing::Level::TRACE)))]
717 fn disable(&self) -> Result<(), WifiError> {
718 let _lock = self
719 .lock
720 .lock()
721 .map_err(|e| WifiError::Lock(format!("Failed to acquire lock: {}", e)))?;
722
723 if !is_module_loaded(self.config.module.as_ref()) {
724 info!("WiFi already disabled, skipping");
725 return Ok(());
726 }
727
728 info!(
729 module = %self.config.module,
730 "Disabling WiFi"
731 );
732
733 self.run_script(WIFI_PRE_DOWN_SCRIPT);
734
735 self.stop_wpa_supplicant()?;
736 self.wlarm_le_down()?;
737 self.ifconfig_down()?;
738
739 std::thread::sleep(std::time::Duration::from_millis(200));
740
741 if self.config.power_toggle != PowerToggle::Wmt {
742 self.rmmod(self.config.module.as_ref())?;
743 if self.config.module == WifiModule::Moal {
744 self.rmmod("mlan")?;
745 }
746 }
747
748 self.power_down()?;
749
750 self.run_script(WIFI_POST_DOWN_SCRIPT);
751
752 info!("WiFi disabled successfully");
753 Ok(())
754 }
755}
756
757#[cfg_attr(feature = "otel", tracing::instrument)]
777pub fn create_wifi_manager() -> Result<Box<dyn WifiManager>, WifiError> {
778 let config = WifiModuleConfig::from_env().ok_or_else(|| {
779 WifiError::DeviceInfo("Missing WIFI_MODULE, PLATFORM, or INTERFACE env".to_string())
780 })?;
781
782 Ok(Box::new(KoboWifiManager::new(config)))
783}