crosvm/devices/src/bat.rs
Chuanxiao Dong 256be3a144 Goldfish battery: add external command interface to set the state
Add a new command "battery" which is used to modify the virtual
battery/ac status.

When there is goldfish battery device model created, the command
is able to sent to the goldfish battery monitoring thread. If no,
the command won't be sent.

The supported commands are:
1. crosvm battery goldfish status <status string> crosvm.sock
2. crosvm battery goldfish health <health string> crosvm.sock
3. crosvm battery goldfish present <number> crosvm.sock
4. crosvm battery goldfish capacity <number> crosvm.sock
5. crosvm battery goldfish aconline <number> crosvm.sock

"goldfish" is the battery identifier to specify which virtual battery
is going to be modified by this command in case there are multiple
virtual batteries in the guest in future. Right now only one goldfish
battery is supported.

BUG=chromium:1050432
BUG=b:137890633
TEST=create VM with parameter "--battery" or "--battery=type=goldfish"
and boot linux guest with goldfish_battery driver enabled. From host
side, execute command "#crosvm battery goldfish status discharging
crosvm.sock" can make the guest receive an interrupt, and the status
sysfs is changed to "Discharging". Also tested the other commands
as well.
TEST=modified values are visible in ARCVM and interrupts are triggered

Change-Id: I82177811a6f2b1960f7895522760ff3b8143163f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2119574
Tested-by: Alex Lau <alexlau@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Alex Lau <alexlau@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
2020-11-12 19:34:04 +00:00

412 lines
13 KiB
Rust

// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::BusDevice;
use acpi_tables::{aml, aml::Aml};
use base::{error, warn, AsRawDescriptor, Descriptor, Event, PollContext, PollToken};
use msg_socket::{MsgReceiver, MsgSender};
use std::fmt::{self, Display};
use std::os::unix::io::RawFd;
use std::sync::Arc;
use std::thread;
use sync::Mutex;
use vm_control::{BatControlCommand, BatControlResponseSocket, BatControlResult};
/// Errors for battery devices.
#[derive(Debug)]
pub enum BatteryError {
Non32BitMmioAddress,
}
impl Display for BatteryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::BatteryError::*;
match self {
Non32BitMmioAddress => write!(f, "Non 32-bit mmio address space"),
}
}
}
type Result<T> = std::result::Result<T, BatteryError>;
/// the GoldFish Battery MMIO length.
pub const GOLDFISHBAT_MMIO_LEN: u64 = 0x1000;
struct GoldfishBatteryState {
// interrupt state
int_status: u32,
int_enable: u32,
// AC state
ac_online: u32,
// Battery state
status: u32,
health: u32,
present: u32,
capacity: u32,
}
macro_rules! create_battery_func {
// $property: the battery property which is going to be modified.
// $int: the interrupt status which is going to be set to notify the guest.
($fn:ident, $property:ident, $int:ident) => {
fn $fn(&mut self, value: u32) -> bool {
let old = std::mem::replace(&mut self.$property, value);
old != self.$property && self.set_int_status($int)
}
};
}
impl GoldfishBatteryState {
fn set_int_status(&mut self, mask: u32) -> bool {
if ((self.int_enable & mask) != 0) && ((self.int_status & mask) == 0) {
self.int_status |= mask;
return true;
}
false
}
fn int_status(&self) -> u32 {
self.int_status
}
create_battery_func!(set_ac_online, ac_online, AC_STATUS_CHANGED);
create_battery_func!(set_status, status, BATTERY_STATUS_CHANGED);
create_battery_func!(set_health, health, BATTERY_STATUS_CHANGED);
create_battery_func!(set_present, present, BATTERY_STATUS_CHANGED);
create_battery_func!(set_capacity, capacity, BATTERY_STATUS_CHANGED);
}
/// GoldFish Battery state
pub struct GoldfishBattery {
state: Arc<Mutex<GoldfishBatteryState>>,
mmio_base: u32,
irq_num: u32,
irq_evt: Event,
irq_resample_evt: Event,
activated: bool,
monitor_thread: Option<thread::JoinHandle<()>>,
kill_evt: Option<Event>,
socket: Option<BatControlResponseSocket>,
}
/// Goldfish Battery MMIO offset
const BATTERY_INT_STATUS: u32 = 0;
const BATTERY_INT_ENABLE: u32 = 0x4;
const BATTERY_AC_ONLINE: u32 = 0x8;
const BATTERY_STATUS: u32 = 0xC;
const BATTERY_HEALTH: u32 = 0x10;
const BATTERY_PRESENT: u32 = 0x14;
const BATTERY_CAPACITY: u32 = 0x18;
const BATTERY_VOLTAGE: u32 = 0x1C;
const BATTERY_TEMP: u32 = 0x20;
const BATTERY_CHARGE_COUNTER: u32 = 0x24;
const BATTERY_VOLTAGE_MAX: u32 = 0x28;
const BATTERY_CURRENT_MAX: u32 = 0x2C;
const BATTERY_CURRENT_NOW: u32 = 0x30;
const BATTERY_CURRENT_AVG: u32 = 0x34;
const BATTERY_CHARGE_FULL_UAH: u32 = 0x38;
const BATTERY_CYCLE_COUNT: u32 = 0x40;
/// Goldfish Battery interrupt bits
const BATTERY_STATUS_CHANGED: u32 = 1 << 0;
const AC_STATUS_CHANGED: u32 = 1 << 1;
const BATTERY_INT_MASK: u32 = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED;
fn command_monitor(
socket: BatControlResponseSocket,
irq_evt: Event,
irq_resample_evt: Event,
kill_evt: Event,
state: Arc<Mutex<GoldfishBatteryState>>,
) {
#[derive(PollToken)]
enum Token {
Commands,
Resample,
Kill,
}
let poll_ctx: PollContext<Token> = match PollContext::build_with(&[
(&Descriptor(socket.as_raw_descriptor()), Token::Commands),
(
&Descriptor(irq_resample_evt.as_raw_descriptor()),
Token::Resample,
),
(&Descriptor(kill_evt.as_raw_descriptor()), Token::Kill),
]) {
Ok(pc) => pc,
Err(e) => {
error!("failed to build PollContext: {}", e);
return;
}
};
'poll: loop {
let events = match poll_ctx.wait() {
Ok(v) => v,
Err(e) => {
error!("error while polling for events: {}", e);
break;
}
};
for event in events.iter_readable() {
match event.token() {
Token::Commands => {
let req = match socket.recv() {
Ok(req) => req,
Err(e) => {
error!("failed to receive request: {}", e);
continue;
}
};
let mut bat_state = state.lock();
let inject_irq = match req {
BatControlCommand::SetStatus(status) => bat_state.set_status(status.into()),
BatControlCommand::SetHealth(health) => bat_state.set_health(health.into()),
BatControlCommand::SetPresent(present) => {
let v = if present != 0 { 1 } else { 0 };
bat_state.set_present(v)
}
BatControlCommand::SetCapacity(capacity) => {
let v = std::cmp::min(capacity, 100);
bat_state.set_capacity(v)
}
BatControlCommand::SetACOnline(ac_online) => {
let v = if ac_online != 0 { 1 } else { 0 };
bat_state.set_ac_online(v)
}
};
if inject_irq {
let _ = irq_evt.write(1);
}
if let Err(e) = socket.send(&BatControlResult::Ok) {
error!("failed to send response: {}", e);
}
}
Token::Resample => {
let _ = irq_resample_evt.read();
if state.lock().int_status() != 0 {
let _ = irq_evt.write(1);
}
}
Token::Kill => break 'poll,
}
}
}
}
impl GoldfishBattery {
/// Create GoldfishBattery device model
///
/// * `mmio_base` - The 32-bit mmio base address.
/// * `irq_num` - The corresponding interrupt number of the irq_evt
/// which will be put into the ACPI DSDT.
/// * `irq_evt` - The interrupt event used to notify driver about
/// the battery properties changing.
/// * `irq_resample_evt` - Resample interrupt event notified at EOI.
/// * `socket` - Battery control socket
pub fn new(
mmio_base: u64,
irq_num: u32,
irq_evt: Event,
irq_resample_evt: Event,
socket: BatControlResponseSocket,
) -> Result<Self> {
if mmio_base + GOLDFISHBAT_MMIO_LEN - 1 > u32::MAX as u64 {
return Err(BatteryError::Non32BitMmioAddress);
}
let state = Arc::new(Mutex::new(GoldfishBatteryState {
capacity: 50,
health: 1,
present: 1,
status: 1,
ac_online: 1,
int_enable: 0,
int_status: 0,
}));
Ok(GoldfishBattery {
state,
mmio_base: mmio_base as u32,
irq_num,
irq_evt,
irq_resample_evt,
activated: false,
monitor_thread: None,
kill_evt: None,
socket: Some(socket),
})
}
/// return the fds used by this device
pub fn keep_fds(&self) -> Vec<RawFd> {
let mut fds = vec![
self.irq_evt.as_raw_descriptor(),
self.irq_resample_evt.as_raw_descriptor(),
];
if let Some(socket) = &self.socket {
fds.push(socket.as_raw_descriptor());
}
fds
}
/// start a monitor thread to monitor the events from host
fn start_monitor(&mut self) {
if self.activated {
return;
}
let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!(
"{}: failed to create kill EventFd pair: {}",
self.debug_label(),
e
);
return;
}
};
if let Some(socket) = self.socket.take() {
let irq_evt = self.irq_evt.try_clone().unwrap();
let irq_resample_evt = self.irq_resample_evt.try_clone().unwrap();
let bat_state = self.state.clone();
let monitor_result = thread::Builder::new()
.name(self.debug_label())
.spawn(move || {
command_monitor(socket, irq_evt, irq_resample_evt, kill_evt, bat_state);
});
self.monitor_thread = match monitor_result {
Err(e) => {
error!(
"{}: failed to spawn PowerIO monitor: {}",
self.debug_label(),
e
);
return;
}
Ok(join_handle) => Some(join_handle),
};
self.kill_evt = Some(self_kill_evt);
self.activated = true;
}
}
}
impl Drop for GoldfishBattery {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do with a failure.
let _ = kill_evt.write(1);
}
if let Some(thread) = self.monitor_thread.take() {
let _ = thread.join();
}
}
}
impl BusDevice for GoldfishBattery {
fn debug_label(&self) -> String {
"GoldfishBattery".to_owned()
}
fn read(&mut self, offset: u64, data: &mut [u8]) {
if data.len() != std::mem::size_of::<u32>() {
warn!(
"{}: unsupported read length {}, only support 4bytes read",
self.debug_label(),
data.len()
);
return;
}
let val = match offset as u32 {
BATTERY_INT_STATUS => {
// read to clear the interrupt status
std::mem::replace(&mut self.state.lock().int_status, 0)
}
BATTERY_INT_ENABLE => self.state.lock().int_enable,
BATTERY_AC_ONLINE => self.state.lock().ac_online,
BATTERY_STATUS => self.state.lock().status,
BATTERY_HEALTH => self.state.lock().health,
BATTERY_PRESENT => self.state.lock().present,
BATTERY_CAPACITY => self.state.lock().capacity,
BATTERY_VOLTAGE => 0,
BATTERY_TEMP => 0,
BATTERY_CHARGE_COUNTER => 0,
BATTERY_VOLTAGE_MAX => 0,
BATTERY_CURRENT_MAX => 0,
BATTERY_CURRENT_NOW => 0,
BATTERY_CURRENT_AVG => 0,
BATTERY_CHARGE_FULL_UAH => 0,
BATTERY_CYCLE_COUNT => 0,
_ => {
warn!("{}: unsupported read offset {}", self.debug_label(), offset);
return;
}
};
let val_arr = val.to_ne_bytes();
data.copy_from_slice(&val_arr);
}
fn write(&mut self, offset: u64, data: &[u8]) {
if data.len() != std::mem::size_of::<u32>() {
warn!(
"{}: unsupported write length {}, only support 4bytes write",
self.debug_label(),
data.len()
);
return;
}
let mut val_arr = u32::to_ne_bytes(0 as u32);
val_arr.copy_from_slice(data);
let val = u32::from_ne_bytes(val_arr);
match offset as u32 {
BATTERY_INT_ENABLE => {
self.state.lock().int_enable = val;
if (val & BATTERY_INT_MASK) != 0 && !self.activated {
self.start_monitor();
}
}
_ => {
warn!("{}: Bad write to offset {}", self.debug_label(), offset);
}
};
}
}
impl Aml for GoldfishBattery {
fn to_aml_bytes(&self, bytes: &mut Vec<u8>) {
aml::Device::new(
"GFBY".into(),
vec![
&aml::Name::new("_HID".into(), &"GFSH0001"),
&aml::Name::new(
"_CRS".into(),
&aml::ResourceTemplate::new(vec![
&aml::Memory32Fixed::new(true, self.mmio_base, GOLDFISHBAT_MMIO_LEN as u32),
&aml::Interrupt::new(true, false, false, true, self.irq_num),
]),
),
],
)
.to_aml_bytes(bytes);
}
}