android emulator虚拟设备分析第一篇之battery

本文使用的android版本是5.1.0_r1,goldfish内核版本是3.4,android镜像是x86架构的。本文以battery为例,介绍虚拟设备的实现方法。为什么android emulator需要虚拟设备,简单来说就是android系统需要使用,但是host系统却没有,比如gps,bluetooth,battery,gsm等。另外,虚拟设备也提供了android emula
摘要由CSDN通过智能技术生成

一、概述

本文使用的android版本是5.1.0_r1,goldfish内核版本是3.4,android镜像是x86架构的。本文以battery为例,完整地介绍了虚拟设备的实现和使用。


为什么android emulator需要虚拟设备,简单来说就是android系统需要使用,但是host系统却没有,比如gps,bluetooth,battery,gsm等。另外,虚拟设备也提供了android emulator和guest os之间交流的方式,比如emulator控制面板中可以设置电池的电量,是否在充电,如图1所示;也可以设置当前的gps坐标等等;更重要的是,将guest os中画图的操作放到了host中执行,android emulator才能够比较流畅的运行guest os。

emulator控制面板

图1


整个虚拟设备的框架如图2所示,左上角是guest os;左下角既包括了kernel中虚拟设备的驱动,也包括了emulator中虚拟设备的模拟;右下角是android emulator。

1、guest os通过hal或者直接使用kernel提供的虚拟设备的驱动(一般来说,虚拟设备的驱动会提供一些字符设备文件,以及属性文件,读写这些文件即可)。

2-1、从kernel的角度来看,无需关心设备是真实的,还是虚拟的,只需要关心设备提供的资源,比如IO资源,中断号,以及如何读写设备的寄存器,这里和普通的驱动程序类似。需要注意的是,虚拟设备都挂在platform bus上,方便动态地分配IO内存空间,以及中断号,当然platform bus本身的IO内存和中断号是固定写死的,和emulator中固定写死的相对应。

2-2、从emulator的角度看,首先是platform bus的模拟,需要使用固定写死的IO内存和中断号,这和kernel是相对应的。然后其他虚拟设备动态注册IO内存和中断号到这个platform bus上面。kernel对IO内存进行读写时,emulator很明显可以得知读写的是哪一个虚拟物理地址,然后得到虚拟页面。虚拟页面有相应的信息,可以得到一个io_index变量,使用这个io_index,可以得知该页面是哪一个虚拟设备的IO内存,以及这个虚拟设备自己的读写函数,使用对应设备的读写函数,读写虚拟设备的寄存器(每个虚拟设备的寄存器都放在一个结构体中),根据约定好的寄存器的功能,去接收/返回数据。这里知识点比较多,且涉及到了很多硬件的知识,对于纯软件的开发人员来说,过于复杂,后面会详细讲解。

3、emulator提供了一种抽象的虚拟设备,叫做pipe,对应的设备文件为/dev/qemu_pipe,提供guest os和emulator通用的数据收发方法。基于这一层通用的数据收发方法,在emulator中注册了很多qemud service,guest os可以通过读写/dev/qemu_pipe去和这些qemud service通信。

PS:

1、guest os中有一个qemud进程,使用虚拟设备ttyS1去提供guest os和emulator的交流方式,是一种旧的方式,速度比较慢,已基本被pipe方式代替。

2、关于platform模型,可以看看这篇资料:http://www.wowotech.net/device_model/platform_device.html

当注册一个新的设备时,会将设备作为参数,probe给每一个匹配的驱动程序,看看哪个驱动程序可以处理这个新的设备。

当注册一个新的驱动时,会将驱动作为参数,probe给每一个未被处理的匹配的设备,看看新的驱动可以处理哪一个未被处理的设备。

通过驱动和设备的名字进行匹配。

虚拟设备架构


二、内核中虚拟设备的驱动程序

2.1、battery的驱动

首先是虚拟设备battery文档的学习:

VII. Goldfish battery:
======================

Relevant files:
  $QEMU/hw/android/goldfish/battery.c
  $QEMU/hw/power_supply.h
  $KERNEL/drivers/power/goldfish_battery.c

Device properties:
  Name: goldfish_battery
  Id: -1
  IrqCount: 1
  I/O Registers:
    0x00 INT_STATUS   R: Read battery and A/C status change bits.
    0x04 INT_ENABLE   W: Enable or disable IRQ on status change.
    0x08 AC_ONLINE    R: Read 0 if AC power disconnected, 1 otherwise.
    0x0c STATUS       R: Read battery status (charging/full/... see below).
    0x10 HEALTH       R: Read battery health (good/overheat/... see below).
    0x14 PRESENT      R: Read 1 if battery is present, 0 otherwise.
    0x18 CAPACITY     R: Read battery charge percentage in [0..100] range.

A simple device used to report the state of the virtual device's battery, and
whether the device is powered through a USB or A/C adapter.

The device uses a single IRQ to notify the kernel that the battery or A/C status
changed. When this happens, the kernel should perform an IO_READ(INT_STATUS)
which returns a 2-bit value containing flags:

  bit 0: Set to 1 to indicate a change in battery status.
  bit 1: Set to 1 to indicate a change in A/C status.

Note that reading this register also lowers the IRQ level.

The A/C status can be read with IO_READ(AC_ONLINE), which returns 1 if the
device is powered, or 0 otherwise.

The battery status is spread over multiple I/O registers:

  IO_READ(PRESENT) returns 1 if the battery is present in the virtual device,
  or 0 otherwise.

  IO_READ(CAPACITY) returns the battery's charge percentage, as an integer
  between 0 and 100, inclusive. NOTE: This register is probably misnamed since
  it does not represent the battery's capacity, but it's current charge level.

  IO_READ(STATUS) returns one of the following values:

    0x00  UNKNOWN      Battery state is unknown.
    0x01  CHARGING     Battery is charging.
    0x02  DISCHARGING  Battery is discharging.
    0x03  NOT_CHARGING Battery is not charging (e.g. full or dead).

  IO_READ(HEALTH) returns one of the following values:

    0x00  UNKNOWN         Battery health unknown.
    0x01  GOOD            Battery is in good condition.
    0x02  OVERHEATING     Battery is over-heating.
    0x03  DEAD            Battery is dead.
    0x04  OVERVOLTAGE     Battery generates too much voltage.
    0x05  UNSPEC_FAILURE  Battery has unspecified failure.

The kernel can use IO_WRITE(INT_ENABLE, <flags>) to select which condition
changes should trigger an IRQ. <flags> is a 2-bit value using the same format
as INT_STATUS.
如果你搞过硬件,可以浏览一下说明,即可知道这个芯片干什么的了,下面一段话无需再看。如果你是搞纯软件的,还是老老实实看吧。

可以把设备当作一个函数,寄存器是它的一些输入数据、返回数据,以及一些状态。中断有点像linux编程中的信号(signal),当设备有数据可读,可以接收数据,状态发生变化等等时,可以(当然,也可以不)产生一个中断,打断内核的执行(CPU硬件上的打断,不是操作系统的调度),跳转到中断处理函数(类似于信号处理函数,信号和信号处理函数对应,中断号和中断处理函数对应)。具体的跳转方式如下:使用内核中的函数request_irq申请中断时,填写中断号和中断函数。内存中有一张表(数组),叫做中断向量表,以中断号为key,以中断函数的地址为value,记录了中断函数的信息。当中断发生时,CPU可以得知中断号,然后通过中断向量表查找到对应的中断处理函数,然后跳转过去执行。真正的中断函数是没有输入参数和返回值,内核中提供的中断函数是经过处理的,所以会有int irq, void *dev_id两个参数。虚拟设备的寄存器的地址都很小,可以理解为偏移量,那么base如何获取呢?首先通过platform bus,得到IO内存的虚拟物理地址,然后使用ioremap将虚拟物理地址映射到内核虚拟地址中,然后可以在内核中使用。注意不能直接当成普通的内存来用,需要使用特殊的readb, writeb, readw, writew, readl, writel,因为硬件的寄存器,每次读取,返回的数据可以是不同的;如果要通过寄存器发送一个数组,那么循环对同一个寄存器进行写操作即可,寄存器地址不用++;另外对于读取和写入的顺序以及操作的宽度(8bit, 16bit or 32bit)也有严格的要求,不是随便来的。如果当成普通内存访问,那么编译器可能会去使用缓存,CPU执行指令可能乱序,以及宽度不对,都会导致硬件工作不正常,所以不能当成普通内存指针去使用。


battery驱动代码在goldfish目录中的drivers/misc/qemupipe/qemu_pipe.c,为了简单起见,注销,关闭,清理的代码就不详细说明了。

驱动的初始化函数是:

static struct platform_driver goldfish_battery_device = {
    .probe      = goldfish_battery_probe,
    .remove     = goldfish_battery_remove,
    .driver = {
        .name = "goldfish-battery"
    }
};

static int __init goldfish_battery_init(void)
{
    return platform_driver_register(&goldfish_battery_device);
}
注册了一个名为goldfish-battery的总线设备,它的probe函数为goldfish_battery_probe,在安装battery驱动,或者总线上有新的设备时会被调用,去匹配驱动程序和设备(根据驱动的名字和设备的名字匹配)。


goldfish_battery_probe先是对goldfish_battery_data结构体进行初始化,然后使用platform_get_resource去获取设备的IO内存资源,对IORESOURCE_MEM资源进行ioremap,然后将base保存到data->reg_base中;然后使用platform_get_irq获取中断号,并保存到data->irq中并使用request_irq函数注册了中断函数goldfish_battery_interrupt。

data->battery和data->ac都是struct power_supply,比如battery:

    data->battery.properties = goldfish_battery_props;
    data->battery.num_properties = ARRAY_SIZE(goldfish_battery_props);
    data->battery.get_property = goldfish_battery_get_property;
    data->battery.name = "battery";
    data->battery.type = POWER_SUPPLY_TYPE_BATTERY;

会有一些属性名,属性个数,读取属性的函数等信息,power_supply_register之后,在guest os的/sys/class/power_supply/battery中会有一些文件,文件名都和属性名对应,比如capacity,health,status等,读函数也就是刚才的goldfish_battery_get_property,写函数没有。guest os用户空间的程序,直接读取这些属性文件,属性文件的内容,都来自于对寄存器的读取,比如

 static int goldfish_battery_get_property(struct power_supply *psy,
              enum power_supply_property psp,
              union power_supply_propval *val)
{
 struct goldfish_battery_data *data = container_of(psy,
     struct goldfish_battery_data, battery);
 int ret = 0;

 switch (psp) {
 case POWER_SUPPLY_PROP_STATUS:
     val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS);
     break;
 case POWER_SUPPLY_PROP_HEALTH:
     val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH);
        break;
    case POWER_SUPPLY_PROP_PRESENT:
        val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT);
        break;
    case POWER_SUPPLY_PROP_TECHNOLOGY:
        val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
        break;
    case POWER_SUPPLY_PROP_CAPACITY:
        val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY);
        break;
    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}

这样就可得到虚拟设备battery的信息。

最后,GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK)写BATTERY_INT_MASK到寄存器BATTERY_INT_ENABLE使能了中断。当battery以及ac的状态发生变化时,虚拟设备将产生中断(这部分代码在emulator中),然后我们的中断函数goldfish_battery_interrupt就会被调用了。

完整的goldfish_battery_probe代码如下:

static int goldfish_battery_probe(struct platform_device *pdev)
{
    int ret;
    struct resource *r;
    struct goldfish_battery_data *data;

    data = kzalloc(sizeof(*data), GFP_KERNEL);
    if (data == NULL) {
      
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值