说明
本文以Crosvm + KVM运行Android虚拟机为例分析Virtio-input框架,该方案谷歌做的比较成熟,支持Android中响应触控屏事件(鼠标,触控都认为是触控),键盘事件等
概述
Android有两种运行形态,一种是运行于Hypervisor之上的虚拟机,另一种是直接运行于硬件的Native Android。
input driver:这两种场景在input驱动层面实现不同,Android虚拟机依赖Virtio-input框架将输入Event上报给Input子系统,Native Android依赖USB驱动和Hid驱动将输入Event上报给Input子系统。
framework inputflinger:不感知这两种场景,处理裸机一致,主要监听读取/dev/input/eventX事件,通过InputDispatcher上报给WindowMnager,分发到对应的winfow和display。
InputFlinger
- Inputflinger流程. InputManger线程启动后,启动InputDispatcher线程和InputReader线程
- InputReader. 由EventHub,TouchInputMapper,QueuedInputListener等组成,
- EventHub打开所有/dev/input/eventX设备并epoll监听,加载idc配置文件,一旦有事件到来就组装为rawEvent放入mEventBuffer
- InputReader将rawEvent组装为NotifyMotionArgs,携带了更多信息,如displayId等
- QueuedInputListener将NotifyMotionArgs组装后填充到mInboundQueue
- InputDispatcher.从mInboundQueue取出Event,根据事件类型进行分发,如触控MOTION事件
#由IMS java调用start()
InputManager::start()
mDispatcher->start() #启动dispatcher线程,分发Event
InputDispatcher::dispatchOnce() #分发线程主体
dispatchOnceInnerLocked #从mInboundQueue取Event根据TYPE分发
EventEntry::Type::MOTION
InputDispatcher::dispatchMotionLocked #分发触控事件
dispatchEventLocked
InputReader->start() #启动inputReader线程,读取Event
InputReader::start()
InputReader::loopOnce
EventHub::getEvents() #从EventHub读取事件,EVENT_BUFFER_SIZE 256
EventHub::readNotifyLocked() //检查event事件
EventHub::scanDirLocked() //检查新目录事件
EventHub::openDeviceLocked() //打开eventX设备,加入epoll
#构造identifier和EventHub::device
loadConfigurationLocked #根据identifier名称打开idc文件
#rk3588可以在此处为每个event指定idc
epoll_wait //有事件后构造event
InputReader::processEventsLocked() #rawEvent放入mEventBuffer
InputDevice::process #将rawEvent处理为NotifyMotionArgs
MultiTouchInputMapper::process
TouchInputMapper::processRawTouches
TouchInputMapper::cookAndDispatch
TouchInputMapper::dispatchTouches
#组装NotifyMotionArgs
#要能获取2个deviceId和displayId
TouchInputMapper::dispatchMotion
QueuedInputListener::notifyMotion
QueuedInputListener::flush() #发送到InputDispatcher线程
InputClassifier::notifyMotion(NotifyMotionArgs const*)
InputDispatcher::notifyMotion #填充Event
enqueueInboundEventLocked #入mInboundQueue,由Dispatcher去取
virtio-input框架
- Virtio-Input框架输入事件上报流程:webrtc -> crosvm virtio-input device --> VirtQueue --> virtio_input.ko -> kernel Input subsystem ->/dev/input/event0 ->Android InputReader
协议
- 配置协议. virtio_input协议简单,前端向后端请求配置,后端填充。配置一般由触发的virtio-pci的read/write操作.前后端支持的配置协议
VIRTIO_INPUT_CFG_UNSET = 0x00,
VIRTIO_INPUT_CFG_ID_NAME = 0x01,
VIRTIO_INPUT_CFG_ID_SERIAL = 0x02,
VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03,
VIRTIO_INPUT_CFG_PROP_BITS = 0x10, //PROP表示支持的properties
VIRTIO_INPUT_CFG_EV_BITS = 0x11, //EV表示是什么设备
VIRTIO_INPUT_CFG_ABS_INFO = 0x12, //绝对值,用于表示坐标
数据传输. virtio_input有event和status两个VirtQueue,eventQueue用于传输event事件,形式为[type,code,value],statusQueue用于传输状态,如crosvm从eventQueue读取了多少字节。没有向Virtio-Gpu那种协议CMD。
前端(front-end)分析
Virtio-input.ko.前端为Android中的Virtio-input.ko,
#代码位于common目录,因为Android沿用开源virtio-input前端驱动,基本没有改动,因此不在common-modules目录
common/drivers/virtio/virtio_input.c
#kernel.log日志,input设备实际由crosvm创建
input: Crosvm Virtio Multitouch Touchscreen 0 as /devices/pci0000:00/0000:00:0c.0/virtio11/input/input0
input: Crosvm Virtio Multitouch Touchscreen 1 as /devices/pci0000:00/0000:00:0d.0/virtio12/input/input1
- 触控调试
#/dev/input/下的设备均是crosvm virtio创建的
#android 中/dev/input/下的设备均是virtio-input创建
#鼠标点击app键(触控操作也是一样),检测input输入
getevent -l
- 代码分析
#前端发送. 向vq发送type,code,value
virtinput_send_status()
#前端接收
virtinput_recv_events()
#从vq接受type,code,value,向input子系统上报事件
#如getevent看到的[EV_ABS ABS_MT_POSITION_Y 0000023d]
input_event()
后端(back-end)分析
-
Crosvm后端.对mouse,keyboard,single_touch_screen,multi_touch_screen输入设备支持,如trout使能了keyboard,switch,multi_touch_screen,以下对multi_touch_screen进行分析
-
配置构造
VirtioInputConfig #用于向前端返回配置
#构造配置内存,根据VIRTIO_INPUT_CFG协议设置cfg,用于拷贝给guest
build_config_memory
VIRTIO_INPUT_CFG_ID_DEVIDS
//设置DEVID,格式(bustype,vendor,product,version)
VIRTIO_INPUT_CFG_ID_DEVIDS
//设置abs,格式(min,max,fuzz,flat)
cfg.set_device_ids(&self.device_ids);
VIRTIO_INPUT_CFG_
#由VirtioDevice.write_config调用,在virtio-pci对bar操作配置空间时调用
read/write //会对cfg进行拷贝
- 工作队列
Worker工作队列,给run线程处理事件,支持以下方法
#从事件源取数据,写到VQ
fill_event_virtqueue
#将event发到guest
send_events
#将event从guest发到source
read_event_virtqueue
#处理statusQ事件,从VQ读状态到source
process_status_queue
- 主线程
Run线程
For wait_event //等待事件发生并处理,支持以下五种事件
EventQAvailable //
send_events
StatusQAvailable //StatusQ可用,取出发给source
process_status_queue
InputEventsAvailable //事件源有event,转发到guest
send_events
InterruptResample //中断可以重采样
Kill
- crosvm设备与android设备绑定
#crosvm multi_touch设备和Android input设备绑定
input : Crosvm Virtio Multitouch Touchscreen 0 as /devices/pci0000:00/0000:00:0c.0/virtio11/input/input0
#根据配置文件将crosvm event和Android输入事件event0绑定,
EventHub: New device: id=2, fd=186, path='/dev/input/event0', name='Crosvm Virtio Multitouch Touchscreen 0', classes=TOUCH | TOUCH_MT, configuration='/vendor/usr/idc/Crosvm_Virtio_Multitouch_Touchscreen_0.idc', keyLayout='', keyCharacterMap='', builtinKeyboard=false,
#crosvm Touchscreen和displayID绑定
InputReader: Device reconfigured: id=3, name='Crosvm Virtio Multitouch Touchscreen 1', size 800x600, orientation 0, mode 1, display id 2