xref: /aosp_15_r20/external/crosvm/devices/src/bat.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2020 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::sync::Arc;
6 
7 use acpi_tables::aml;
8 use acpi_tables::aml::Aml;
9 use anyhow::bail;
10 use anyhow::Context;
11 use base::error;
12 use base::warn;
13 use base::AsRawDescriptor;
14 use base::Event;
15 use base::EventToken;
16 use base::RawDescriptor;
17 use base::Tube;
18 use base::WaitContext;
19 use base::WorkerThread;
20 use power_monitor::BatteryStatus;
21 use power_monitor::CreatePowerClientFn;
22 use power_monitor::CreatePowerMonitorFn;
23 use remain::sorted;
24 use serde::Deserialize;
25 use serde::Serialize;
26 use sync::Mutex;
27 use thiserror::Error;
28 use vm_control::BatConfig;
29 use vm_control::BatControlCommand;
30 use vm_control::BatControlResult;
31 
32 use crate::pci::CrosvmDeviceId;
33 use crate::BusAccessInfo;
34 use crate::BusDevice;
35 use crate::DeviceId;
36 use crate::IrqLevelEvent;
37 use crate::Suspendable;
38 
39 /// Errors for battery devices.
40 #[sorted]
41 #[derive(Error, Debug)]
42 pub enum BatteryError {
43     #[error("Non 32-bit mmio address space")]
44     Non32BitMmioAddress,
45 }
46 
47 type Result<T> = std::result::Result<T, BatteryError>;
48 
49 /// the GoldFish Battery MMIO length.
50 pub const GOLDFISHBAT_MMIO_LEN: u64 = 0x1000;
51 
52 #[derive(Clone, Serialize, Deserialize)]
53 struct GoldfishBatteryState {
54     // interrupt state
55     int_status: u32,
56     int_enable: u32,
57     // AC state
58     ac_online: u32,
59     // Battery state
60     status: u32,
61     health: u32,
62     present: u32,
63     capacity: u32,
64     voltage: u32,
65     current: u32,
66     charge_counter: u32,
67     charge_full: u32,
68     initialized: bool,
69 }
70 
71 macro_rules! create_battery_func {
72     // $property: the battery property which is going to be modified.
73     // $int: the interrupt status which is going to be set to notify the guest.
74     ($fn:ident, $property:ident, $int:ident) => {
75         pub(crate) fn $fn(&mut self, value: u32) -> bool {
76             let old = std::mem::replace(&mut self.$property, value);
77             old != self.$property && self.set_int_status($int)
78         }
79     };
80 }
81 
82 impl GoldfishBatteryState {
set_int_status(&mut self, mask: u32) -> bool83     fn set_int_status(&mut self, mask: u32) -> bool {
84         if ((self.int_enable & mask) != 0) && ((self.int_status & mask) == 0) {
85             self.int_status |= mask;
86             return true;
87         }
88         false
89     }
90 
int_status(&self) -> u3291     fn int_status(&self) -> u32 {
92         self.int_status
93     }
94 
95     create_battery_func!(set_ac_online, ac_online, AC_STATUS_CHANGED);
96 
97     create_battery_func!(set_status, status, BATTERY_STATUS_CHANGED);
98 
99     create_battery_func!(set_health, health, BATTERY_STATUS_CHANGED);
100 
101     create_battery_func!(set_present, present, BATTERY_STATUS_CHANGED);
102 
103     create_battery_func!(set_capacity, capacity, BATTERY_STATUS_CHANGED);
104 
105     create_battery_func!(set_voltage, voltage, BATTERY_STATUS_CHANGED);
106 
107     create_battery_func!(set_current, current, BATTERY_STATUS_CHANGED);
108 
109     create_battery_func!(set_charge_counter, charge_counter, BATTERY_STATUS_CHANGED);
110 
111     create_battery_func!(set_charge_full, charge_full, BATTERY_STATUS_CHANGED);
112 }
113 
114 /// GoldFish Battery state
115 pub struct GoldfishBattery {
116     state: Arc<Mutex<GoldfishBatteryState>>,
117     mmio_base: u32,
118     irq_num: u32,
119     irq_evt: IrqLevelEvent,
120     activated: bool,
121     monitor_thread: Option<WorkerThread<()>>,
122     tube: Option<Tube>,
123     create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
124     create_powerd_client: Option<Box<dyn CreatePowerClientFn>>,
125     // battery_config is used for goldfish battery to report fake battery to the guest.
126     battery_config: Arc<Mutex<BatConfig>>,
127 }
128 
129 #[derive(Serialize, Deserialize)]
130 struct GoldfishBatterySnapshot {
131     state: GoldfishBatteryState,
132     mmio_base: u32,
133     irq_num: u32,
134     activated: bool,
135 }
136 
137 /// Goldfish Battery MMIO offset
138 const BATTERY_INT_STATUS: u32 = 0;
139 const BATTERY_INT_ENABLE: u32 = 0x4;
140 const BATTERY_AC_ONLINE: u32 = 0x8;
141 const BATTERY_STATUS: u32 = 0xC;
142 const BATTERY_HEALTH: u32 = 0x10;
143 const BATTERY_PRESENT: u32 = 0x14;
144 const BATTERY_CAPACITY: u32 = 0x18;
145 const BATTERY_VOLTAGE: u32 = 0x1C;
146 const BATTERY_TEMP: u32 = 0x20;
147 const BATTERY_CHARGE_COUNTER: u32 = 0x24;
148 const BATTERY_VOLTAGE_MAX: u32 = 0x28;
149 const BATTERY_CURRENT_MAX: u32 = 0x2C;
150 const BATTERY_CURRENT_NOW: u32 = 0x30;
151 const BATTERY_CURRENT_AVG: u32 = 0x34;
152 const BATTERY_CHARGE_FULL_UAH: u32 = 0x38;
153 const BATTERY_CYCLE_COUNT: u32 = 0x40;
154 
155 /// Goldfish Battery interrupt bits
156 const BATTERY_STATUS_CHANGED: u32 = 1 << 0;
157 const AC_STATUS_CHANGED: u32 = 1 << 1;
158 const BATTERY_INT_MASK: u32 = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED;
159 
160 /// Goldfish Battery status
161 const BATTERY_STATUS_VAL_UNKNOWN: u32 = 0;
162 const BATTERY_STATUS_VAL_CHARGING: u32 = 1;
163 const BATTERY_STATUS_VAL_DISCHARGING: u32 = 2;
164 const BATTERY_STATUS_VAL_NOT_CHARGING: u32 = 3;
165 
166 /// Goldfish Battery health
167 const BATTERY_HEALTH_VAL_UNKNOWN: u32 = 0;
168 
169 // Goldfish ac online status
170 const AC_ONLINE_VAL_OFFLINE: u32 = 0;
171 
172 #[derive(EventToken)]
173 pub(crate) enum Token {
174     Commands,
175     Resample,
176     Kill,
177     Monitor,
178 }
179 
command_monitor( tube: Tube, irq_evt: IrqLevelEvent, kill_evt: Event, state: Arc<Mutex<GoldfishBatteryState>>, create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>, battery_config: Arc<Mutex<BatConfig>>, )180 fn command_monitor(
181     tube: Tube,
182     irq_evt: IrqLevelEvent,
183     kill_evt: Event,
184     state: Arc<Mutex<GoldfishBatteryState>>,
185     create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
186     battery_config: Arc<Mutex<BatConfig>>,
187 ) {
188     let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
189         (&tube, Token::Commands),
190         (irq_evt.get_resample(), Token::Resample),
191         (&kill_evt, Token::Kill),
192     ]) {
193         Ok(pc) => pc,
194         Err(e) => {
195             error!("failed to build WaitContext: {}", e);
196             return;
197         }
198     };
199 
200     let mut power_monitor = match create_power_monitor {
201         Some(f) => match f() {
202             Ok(p) => match wait_ctx.add(p.get_read_notifier(), Token::Monitor) {
203                 Ok(()) => Some(p),
204                 Err(e) => {
205                     error!("failed to add power monitor to poll context: {}", e);
206                     None
207                 }
208             },
209             Err(e) => {
210                 error!("failed to create power monitor: {}", e);
211                 None
212             }
213         },
214         None => None,
215     };
216 
217     'poll: loop {
218         let events = match wait_ctx.wait() {
219             Ok(v) => v,
220             Err(e) => {
221                 error!("error while polling for events: {}", e);
222                 break;
223             }
224         };
225 
226         for event in events.iter().filter(|e| e.is_readable) {
227             match event.token {
228                 Token::Commands => {
229                     let req = match tube.recv() {
230                         Ok(req) => req,
231                         Err(e) => {
232                             error!("failed to receive request: {}", e);
233                             continue;
234                         }
235                     };
236 
237                     let mut bat_config = battery_config.lock();
238                     let mut bat_state = state.lock();
239                     let inject_irq = match req {
240                         BatControlCommand::SetStatus(status) => bat_state.set_status(status.into()),
241                         BatControlCommand::SetHealth(health) => bat_state.set_health(health.into()),
242                         BatControlCommand::SetPresent(present) => {
243                             let v = present != 0;
244                             bat_state.set_present(v.into())
245                         }
246                         BatControlCommand::SetCapacity(capacity) => {
247                             let v = std::cmp::min(capacity, 100);
248                             bat_state.set_capacity(v)
249                         }
250                         BatControlCommand::SetACOnline(ac_online) => {
251                             let v = ac_online != 0;
252                             bat_state.set_ac_online(v.into())
253                         }
254                         BatControlCommand::SetFakeBatConfig(max_capacity) => {
255                             let max_capacity = std::cmp::min(max_capacity, 100);
256                             *bat_config = BatConfig::Fake { max_capacity };
257                             true
258                         }
259                         BatControlCommand::CancelFakeConfig => {
260                             *bat_config = BatConfig::Real;
261                             true
262                         }
263                     };
264 
265                     if inject_irq {
266                         let _ = irq_evt.trigger();
267                     }
268 
269                     if let Err(e) = tube.send(&BatControlResult::Ok) {
270                         error!("failed to send response: {}", e);
271                     }
272                 }
273 
274                 Token::Monitor => {
275                     // Safe because power_monitor must be populated if Token::Monitor is triggered.
276                     let power_monitor = power_monitor.as_mut().unwrap();
277 
278                     let data = match power_monitor.read_message() {
279                         Ok(Some(d)) => d,
280                         Ok(None) => continue,
281                         Err(e) => {
282                             error!("failed to read new power data: {}", e);
283                             continue;
284                         }
285                     };
286 
287                     let mut bat_state = state.lock();
288 
289                     // Each set_* function called below returns true when interrupt bits
290                     // (*_STATUS_CHANGED) changed. If `inject_irq` is true after we attempt to
291                     // update each field, inject an interrupt.
292                     let mut inject_irq = bat_state.set_ac_online(data.ac_online.into());
293 
294                     match data.battery {
295                         Some(battery_data) => {
296                             inject_irq |= bat_state.set_capacity(battery_data.percent);
297                             let battery_status = match battery_data.status {
298                                 BatteryStatus::Unknown => BATTERY_STATUS_VAL_UNKNOWN,
299                                 BatteryStatus::Charging => BATTERY_STATUS_VAL_CHARGING,
300                                 BatteryStatus::Discharging => BATTERY_STATUS_VAL_DISCHARGING,
301                                 BatteryStatus::NotCharging => BATTERY_STATUS_VAL_NOT_CHARGING,
302                             };
303                             inject_irq |= bat_state.set_status(battery_status);
304                             inject_irq |= bat_state.set_voltage(battery_data.voltage);
305                             inject_irq |= bat_state.set_current(battery_data.current);
306                             inject_irq |= bat_state.set_charge_counter(battery_data.charge_counter);
307                             inject_irq |= bat_state.set_charge_full(battery_data.charge_full);
308                         }
309                         None => {
310                             inject_irq |= bat_state.set_present(0);
311                         }
312                     }
313 
314                     if inject_irq {
315                         let _ = irq_evt.trigger();
316                     }
317                 }
318 
319                 Token::Resample => {
320                     irq_evt.clear_resample();
321                     if state.lock().int_status() != 0 {
322                         let _ = irq_evt.trigger();
323                     }
324                 }
325 
326                 Token::Kill => break 'poll,
327             }
328         }
329     }
330 }
331 
332 impl GoldfishBattery {
333     /// Create GoldfishBattery device model
334     ///
335     /// * `mmio_base` - The 32-bit mmio base address.
336     /// * `irq_num` - The corresponding interrupt number of the irq_evt which will be put into the
337     ///   ACPI DSDT.
338     /// * `irq_evt` - The interrupt event used to notify driver about the battery properties
339     ///   changing.
340     /// * `socket` - Battery control socket
new( mmio_base: u64, irq_num: u32, irq_evt: IrqLevelEvent, tube: Tube, create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>, create_powerd_client: Option<Box<dyn CreatePowerClientFn>>, ) -> Result<Self>341     pub fn new(
342         mmio_base: u64,
343         irq_num: u32,
344         irq_evt: IrqLevelEvent,
345         tube: Tube,
346         create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
347         create_powerd_client: Option<Box<dyn CreatePowerClientFn>>,
348     ) -> Result<Self> {
349         if mmio_base + GOLDFISHBAT_MMIO_LEN - 1 > u32::MAX as u64 {
350             return Err(BatteryError::Non32BitMmioAddress);
351         }
352         let state = Arc::new(Mutex::new(GoldfishBatteryState {
353             capacity: 50,
354             health: BATTERY_HEALTH_VAL_UNKNOWN,
355             present: 1,
356             status: BATTERY_STATUS_VAL_UNKNOWN,
357             ac_online: 1,
358             int_enable: 0,
359             int_status: 0,
360             voltage: 0,
361             current: 0,
362             charge_counter: 0,
363             charge_full: 0,
364             initialized: false,
365         }));
366 
367         let battery_config = Arc::new(Mutex::new(BatConfig::default()));
368 
369         Ok(GoldfishBattery {
370             state,
371             mmio_base: mmio_base as u32,
372             irq_num,
373             irq_evt,
374             activated: false,
375             monitor_thread: None,
376             tube: Some(tube),
377             create_power_monitor,
378             create_powerd_client,
379             battery_config,
380         })
381     }
382 
383     /// return the descriptors used by this device
keep_rds(&self) -> Vec<RawDescriptor>384     pub fn keep_rds(&self) -> Vec<RawDescriptor> {
385         let mut rds = vec![
386             self.irq_evt.get_trigger().as_raw_descriptor(),
387             self.irq_evt.get_resample().as_raw_descriptor(),
388         ];
389 
390         if let Some(tube) = &self.tube {
391             rds.push(tube.as_raw_descriptor());
392         }
393 
394         rds
395     }
396 
397     /// start a monitor thread to monitor the events from host
start_monitor(&mut self)398     fn start_monitor(&mut self) {
399         if self.activated {
400             return;
401         }
402 
403         if let Some(tube) = self.tube.take() {
404             let irq_evt = self.irq_evt.try_clone().unwrap();
405             let bat_state = self.state.clone();
406             let create_monitor_fn = self.create_power_monitor.take();
407             let battery_config = self.battery_config.clone();
408             self.monitor_thread = Some(WorkerThread::start(self.debug_label(), move |kill_evt| {
409                 command_monitor(
410                     tube,
411                     irq_evt,
412                     kill_evt,
413                     bat_state,
414                     create_monitor_fn,
415                     battery_config,
416                 )
417             }));
418             self.activated = true;
419         }
420     }
421 
initialize_battery_state(&mut self) -> anyhow::Result<()>422     fn initialize_battery_state(&mut self) -> anyhow::Result<()> {
423         let mut power_client = match &self.create_powerd_client {
424             Some(f) => match f() {
425                 Ok(c) => c,
426                 Err(e) => bail!("failed to connect to the powerd: {:#}", e),
427             },
428             None => return Ok(()),
429         };
430         match power_client.get_power_data() {
431             Ok(data) => {
432                 let mut bat_state = self.state.lock();
433                 bat_state.set_ac_online(data.ac_online.into());
434 
435                 match data.battery {
436                     Some(battery_data) => {
437                         bat_state.set_capacity(battery_data.percent);
438                         let battery_status = match battery_data.status {
439                             BatteryStatus::Unknown => BATTERY_STATUS_VAL_UNKNOWN,
440                             BatteryStatus::Charging => BATTERY_STATUS_VAL_CHARGING,
441                             BatteryStatus::Discharging => BATTERY_STATUS_VAL_DISCHARGING,
442                             BatteryStatus::NotCharging => BATTERY_STATUS_VAL_NOT_CHARGING,
443                         };
444                         bat_state.set_status(battery_status);
445                         bat_state.set_voltage(battery_data.voltage);
446                         bat_state.set_current(battery_data.current);
447                         bat_state.set_charge_counter(battery_data.charge_counter);
448                         bat_state.set_charge_full(battery_data.charge_full);
449                     }
450                     None => {
451                         bat_state.set_present(0);
452                     }
453                 }
454                 Ok(())
455             }
456             Err(e) => {
457                 bail!("failed to get response from powerd: {:#}", e);
458             }
459         }
460     }
461 }
462 
463 impl Drop for GoldfishBattery {
drop(&mut self)464     fn drop(&mut self) {
465         if let Err(e) = self.sleep() {
466             error!("{}", e);
467         };
468     }
469 }
470 
471 impl BusDevice for GoldfishBattery {
device_id(&self) -> DeviceId472     fn device_id(&self) -> DeviceId {
473         CrosvmDeviceId::GoldfishBattery.into()
474     }
475 
debug_label(&self) -> String476     fn debug_label(&self) -> String {
477         "GoldfishBattery".to_owned()
478     }
479 
read(&mut self, info: BusAccessInfo, data: &mut [u8])480     fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
481         if data.len() != std::mem::size_of::<u32>() {
482             warn!(
483                 "{}: unsupported read length {}, only support 4bytes read",
484                 self.debug_label(),
485                 data.len()
486             );
487             return;
488         }
489 
490         // Before first read, we try to ask powerd the actual power data to initialize `self.state`.
491         if !self.state.lock().initialized {
492             match self.initialize_battery_state() {
493                 Ok(()) => self.state.lock().initialized = true,
494                 Err(e) => {
495                     error!(
496                         "{}: failed to get power data and update: {:#}",
497                         self.debug_label(),
498                         e
499                     );
500                 }
501             }
502         }
503 
504         let val = match info.offset as u32 {
505             BATTERY_INT_STATUS => {
506                 // read to clear the interrupt status
507                 std::mem::replace(&mut self.state.lock().int_status, 0)
508             }
509             BATTERY_INT_ENABLE => self.state.lock().int_enable,
510             BATTERY_AC_ONLINE => match *self.battery_config.lock() {
511                 BatConfig::Real => self.state.lock().ac_online,
512                 BatConfig::Fake { max_capacity: _ } => AC_ONLINE_VAL_OFFLINE,
513             },
514             BATTERY_STATUS => match *self.battery_config.lock() {
515                 BatConfig::Real => self.state.lock().status,
516                 BatConfig::Fake { max_capacity: _ } => BATTERY_STATUS_VAL_DISCHARGING,
517             },
518             BATTERY_HEALTH => self.state.lock().health,
519             BATTERY_PRESENT => self.state.lock().present,
520             BATTERY_CAPACITY => {
521                 let max_capacity = match *self.battery_config.lock() {
522                     BatConfig::Real => 100,
523                     BatConfig::Fake { max_capacity } => max_capacity,
524                 };
525                 std::cmp::min(max_capacity, self.state.lock().capacity)
526             }
527             BATTERY_VOLTAGE => self.state.lock().voltage,
528             BATTERY_TEMP => 0,
529             BATTERY_CHARGE_COUNTER => self.state.lock().charge_counter,
530             BATTERY_VOLTAGE_MAX => 0,
531             BATTERY_CURRENT_MAX => 0,
532             BATTERY_CURRENT_NOW => self.state.lock().current,
533             BATTERY_CURRENT_AVG => 0,
534             BATTERY_CHARGE_FULL_UAH => self.state.lock().charge_full,
535             BATTERY_CYCLE_COUNT => 0,
536             _ => {
537                 warn!("{}: unsupported read address {}", self.debug_label(), info);
538                 return;
539             }
540         };
541 
542         let val_arr = val.to_ne_bytes();
543         data.copy_from_slice(&val_arr);
544     }
545 
write(&mut self, info: BusAccessInfo, data: &[u8])546     fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
547         if data.len() != std::mem::size_of::<u32>() {
548             warn!(
549                 "{}: unsupported write length {}, only support 4bytes write",
550                 self.debug_label(),
551                 data.len()
552             );
553             return;
554         }
555 
556         let mut val_arr = u32::to_ne_bytes(0u32);
557         val_arr.copy_from_slice(data);
558         let val = u32::from_ne_bytes(val_arr);
559 
560         match info.offset as u32 {
561             BATTERY_INT_ENABLE => {
562                 self.state.lock().int_enable = val;
563                 if (val & BATTERY_INT_MASK) != 0 && !self.activated {
564                     self.start_monitor();
565                 }
566             }
567             _ => {
568                 warn!("{}: Bad write to address {}", self.debug_label(), info);
569             }
570         };
571     }
572 }
573 
574 impl Aml for GoldfishBattery {
to_aml_bytes(&self, bytes: &mut Vec<u8>)575     fn to_aml_bytes(&self, bytes: &mut Vec<u8>) {
576         aml::Device::new(
577             "GFBY".into(),
578             vec![
579                 &aml::Name::new("_HID".into(), &"GFSH0001"),
580                 &aml::Name::new(
581                     "_CRS".into(),
582                     &aml::ResourceTemplate::new(vec![
583                         &aml::Memory32Fixed::new(true, self.mmio_base, GOLDFISHBAT_MMIO_LEN as u32),
584                         &aml::Interrupt::new(true, false, false, true, self.irq_num),
585                     ]),
586                 ),
587             ],
588         )
589         .to_aml_bytes(bytes);
590     }
591 }
592 
593 impl Suspendable for GoldfishBattery {
sleep(&mut self) -> anyhow::Result<()>594     fn sleep(&mut self) -> anyhow::Result<()> {
595         if let Some(thread) = self.monitor_thread.take() {
596             thread.stop();
597         }
598         Ok(())
599     }
600 
wake(&mut self) -> anyhow::Result<()>601     fn wake(&mut self) -> anyhow::Result<()> {
602         if self.activated {
603             // Set activated to false for start_monitor to start monitoring again.
604             self.activated = false;
605             self.start_monitor();
606         }
607         Ok(())
608     }
609 
snapshot(&mut self) -> anyhow::Result<serde_json::Value>610     fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> {
611         serde_json::to_value(GoldfishBatterySnapshot {
612             state: self.state.lock().clone(),
613             mmio_base: self.mmio_base,
614             irq_num: self.irq_num,
615             activated: self.activated,
616         })
617         .context("failed to snapshot GoldfishBattery")
618     }
619 
restore(&mut self, data: serde_json::Value) -> anyhow::Result<()>620     fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> {
621         let deser: GoldfishBatterySnapshot =
622             serde_json::from_value(data).context("failed to deserialize GoldfishBattery")?;
623         {
624             let mut locked_state = self.state.lock();
625             *locked_state = deser.state;
626         }
627         self.mmio_base = deser.mmio_base;
628         self.irq_num = deser.irq_num;
629         self.activated = deser.activated;
630         Ok(())
631     }
632 }
633 
634 #[cfg(test)]
635 mod tests {
636     use super::*;
637     use crate::suspendable_tests;
638 
modify_device(battery: &mut GoldfishBattery)639     fn modify_device(battery: &mut GoldfishBattery) {
640         let mut state = battery.state.lock();
641         state.set_capacity(70);
642     }
643 
644     suspendable_tests! {
645         battery, GoldfishBattery::new(
646             0,
647             0,
648             IrqLevelEvent::new().unwrap(),
649             Tube::pair().unwrap().1,
650             None,
651             None,
652         ).unwrap(),
653         modify_device
654     }
655 }
656