这个我们应该分成几大块来说吧, kdrive和xorg的处理是很类似的。 kdrive的驱动都在这个目录下面 hw/kdrive/linux 大家看到有键盘,鼠标,触摸屏,evdev等等的驱动。 其实说白了,在linux系统上面,驱动是分好几部分的,如果我们从最上层来看,我们看到的是图形界面,也就是xserver,实际上xserver这里有这里的驱动层。 以input为例吧,我们看看这个地方。 http://xorg./releases/individual/driver/ 这个地方凡是input的,都是input的设备的驱动,自然这个只是xserver的驱动,而xserver的驱动是建立在底层驱动基础之上的, 我们可以简单的理解为,xserver的驱动实际上只是提供给xserver一个配置,过滤,或者改写,或者fake这些事件的一种机制。 至于底层的驱动,就是我们通常所说的LDD(linux device driver)了,或者可以这么理解,如果要xserver接收到事件,我们需要在底层驱动和xserver之间做一个中继。 至于这个大体的流程,可以简单的描述如下, 内核里面的驱动一般来说会提供一个设备文件,无论是最早的 2.4的内核的驱动,或者是 2.6的驱动,本质并没有变化, 以前我们使用register_char_device类似这样的函数,而现在我们可能使用platform_register_device. 以前我们需要手动在/dev/目录下面创建设备节点,现在udev会自动帮助我们创建这个节点。但是本质来说并没有变化。 还是设备文件。至于设备文件为什么与普通文件不同,这个是由文件系统来实现的。每个文件系统有不同,如果有兴趣自己看看内核代码吧。 linux下面的东西都是建立在文件系统的基础之上的。或者说至少这是一个目标吧。 说多了, /dev/input/event0 现在的设备驱动慢慢都使用evdev的框架了,所以我们以evdev为例来说明, 比如这个设备,是一个输入设备,我们先假定这个是一个键盘吧,实际上这里看不出来的,可以到sysfs里面去看,到底是什么东西。 这个是底层驱动提供的一个设备文件。通过kobject发送uevnt到用户空间,然后udev会根据这个uevent来创建设备文件。所以这个major,minor device 就不用我们担心了。 然后xserver的驱动呢,实际上会打开这些设备文件来读,然后向xserver的event queue里面汇报这些事件。然后xserver会把这些事件发送到对应的window去, 这样对应的window就会收到事件了, xserver的驱动汇报事件的函数很简单,对于kdrive来说。 鼠标 KdEnqueuePointerEvent 示例如下 KdEnqueuePointerEvent(pi, flags, ke->rel[a], 0, 0); 键盘 KdEnqueueKeyboardEvent 示例如下 KdEnqueueKeyboardEvent (ki, events[i].code, !events[i].value); 对于键盘来说比较简单, 我们要带一个设备信息,这样xserver知道这个事件是从谁来的,一个按键的键值,知道是哪个按键,一个value,是按下,还是弹起,或者是repeat 鼠标就比较简单了, 就是坐标,是相对的还是绝对的。 但是新的xserver 1.6.0里面和1.5.3已经有了变化,参数的个数变了,所以驱动要更新一下。 因为kdrive和xorg本身这里处理基本是一样的,我们就简单点,以xorg的来说明,假定现在我们只有/dev/input/event0 基本的流程就是 a)读取/dev/input/event0,注意这里是非阻塞的,稍后说明 b)读取到事件之后,我们就report 其他的还有一些都是外围的处理,重要性相对较低,比如打开设备文件了,或者关闭了等等。 为什么这里的读是非阻塞的,原因是这样的,因为xserver本身要处理很多设备文件,比如我们可能同时有鼠标键盘,触摸屏,手写板等等,所以这里是不能被阻塞的, 我们只能是非阻塞的读,那么这里xserver的这个input的机制到底是怎么样的呢,实际上这里用的是信号处理,只要有SigIO来的时候,就会调用所有的设备的read的函数 这个信息实际上是存在一个数组里面的,每个fd有自己的read函数,当Sigio到来的时候,每个read函数就会读自己的fd,具体的代码在这个地方。 以1.6.0的代码为例 xorg的实现是在这个文件里面 hw/xfree86/os-support/shared/sigio.c static void xf86SIGIO (int sig) { int i; fd_set ready; struct timeval to; int save_errno = errno; /* do not clobber the global errno */ int r; ready = xf86SigIOMask; to.tv_sec = 0; to.tv_usec = 0; SYSCALL (r = select (xf86SigIOMaxFd, &ready, 0, 0, &to)); for (i = 0; r > 0 && i < xf86SigIOMax; i++) if (xf86SigIOFuncs[i].f && FD_ISSET (xf86SigIOFuncs[i].fd, &ready)) { (*xf86SigIOFuncs[i].f)(xf86SigIOFuncs[i].fd, xf86SigIOFuncs[i].closure); r--; } if (r > 0) { xf86Msg(X_ERROR, "SIGIO %d descriptors not handled\n", r); } /* restore global errno */ errno = save_errno; } kdrive的实现相当精简,也很容易看懂在kinput.c里面 static void KdSigio (int sig) { int i; for (i = 0; i < kdNumInputFds; i++) (*kdInputFds[i].read) (kdInputFds[i].fd, kdInputFds[i].closure); } 大家要问这些read是什么时候注册的或者说这些fd是什么时候打开的。 这个和xserver的输入设备自动探测有关。 以kdrive为例。 linux平台上面,设备自动探测无非是和下面的几个因素有关。 kernel driver(这个提供kobject uevent) udev hald dbus 大概就是上面几个要素。 所以xserver也不例外,是通过dbus和hald来实现的。 代码在xserver的根路径的config目录下面,主要和两个文件有关 hal.c dbus.c 很明显了,xserver起来的时候首先就要驱动所有的输入设备,这个没有hald是不行的,这个和所有的volume manager是一样的。 起来的时候就要自动探测所有的块设备,然后mount 还有另外一个过程,就是xserver起来之后,再热插拔设备,这个是需要dbus消息的。 hal.c里面的核心代码在 libhal_ctx_set_device_added(info->hal_ctx, device_added); libhal_ctx_set_device_removed(info->hal_ctx, device_removed); devices = libhal_find_device_by_capability(info->hal_ctx, "input", &num_devices, &error); /* FIXME: Get default devices if error is set. */ for (i = 0; i < num_devices; i++) device_added(info->hal_ctx, devices[i]); 也就是说只要有input属性的设备到来,就会调用device_added 然后就会调用NewInputDeviceRequest 注意这里还有一个过程,就是NewInputDeviceRequest之前,我们是需要设置一些属性的,就是,比如这个设备来了,我们要使用什么驱动来驱动这个设备。 这个东西是从hal里面获取到的,比如鼠标键盘我们现在都基本使用了evdev的driver,所以这个配置是在hald的配置里面,就是hald的那堆fdi的配置里面 比如发现是键盘就merge_key evdev这样的,所以xorg要支持热插拔是需要hald的支持的,包括驱动的种类是在hald里面设定的。 NewInputDeviceRequest这个函数的话,不同的xserver实现就不一样了,xorg有xorg的实现在 hw/xfree86/common/xf86Xinput.c xf86NewInputDevice 具体的,驱动里面enable的时候会注册,比如xf86-input-evdev里面在evdevon这个函数里面 xf86AddEnabledDevice 这个是xorg提供的函数 _X_EXPORT void xf86AddEnabledDevice(InputInfoPtr pInfo) { if (!xf86InstallSIGIOHandler (pInfo->fd, xf86SigioReadInput, pInfo)) { AddEnabledDevice(pInfo->fd); } } 下面就很容易理解了。 _X_EXPORT void AddEnabledDevice(int fd) { FD_SET(fd, &EnabledDevices); AddGeneralSocket(fd); } xorg里面默认的读的函数是这个。 xf86SigioReadInput static void xf86SigioReadInput(int fd, void *closure) { int errno_save = errno; int sigstate = xf86BlockSIGIO(); InputInfoPtr pInfo = (InputInfoPtr) closure; pInfo->read_input(pInfo); xf86UnblockSIGIO(sigstate); errno = errno_save; } 而这个函数就是input驱动来注册的。 比如evdev里面是这个函数EvdevReadInput static InputInfoPtr EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags) { InputInfoPtr pInfo; const char *device; EvdevPtr pEvdev; if (!(pInfo = xf86AllocateInput(drv, 0))) return NULL; /* Initialise the InputInfoRec. */ pInfo->name = dev->identifier; pInfo->flags = 0; pInfo->type_name = "UNKNOWN"; pInfo->device_control = EvdevProc; pInfo->read_input = EvdevReadInput; pInfo->history_size = 0; pInfo->control_proc = NULL; pInfo->close_proc = NULL; pInfo->switch_mode = NULL; pInfo->conversion_proc = NULL; pInfo->reverse_conversion_proc = NULL; pInfo->dev = NULL; pInfo->private_flags = 0; pInfo->always_core_feedback = NULL; pInfo->conf_idev = dev; 而kdrive的实现是在 hw/kdrive/src/kinput.c kdrive的实现很简单看属性,然后调用下面的某个 KdNewPointer KdNewKeyboard 然后添加设备 KdAddPointer 或者 KdAddKeyboard 以keyboard为例 KdAddKeyboard (KdKeyboardInfo *ki) { KdKeyboardInfo **prev; if (!ki) return !Success; ki->dixdev = AddInputDevice(serverClient, KdKeyboardProc, TRUE); if (!ki->dixdev) { ErrorF("Couldn't register keyboard device %s\n", ki->name ? ki->name : "(unnamed)"); return !Success; } RegisterOtherDevice(ki->dixdev); #ifdef DEBUG ErrorF("added keyboard %s with dix id %d\n", ki->name, ki->dixdev->id); #endif for (prev = &kdKeyboards; *prev; prev = &(*prev)->next); *prev = ki; return Success; } 至于注册fd是这个函数,当然这个是在kdrive的驱动evdev里面的例子 if (!KdRegisterFd (fd, EvdevPtrRead, pi)) { Bool KdRegisterFd (int fd, void (*read) (int fd, void *closure), void *closure) { if (kdNumInputFds == KD_MAX_INPUT_FDS) return FALSE; kdInputFds[kdNumInputFds].fd = fd; kdInputFds[kdNumInputFds].read = read; kdInputFds[kdNumInputFds].enable = 0; kdInputFds[kdNumInputFds].disable = 0; kdInputFds[kdNumInputFds].closure = closure; kdNumInputFds++; if (kdInputEnabled) KdAddFd (fd); return TRUE; } 这样就把info放到了数组里面,这样sigio来的时候我们就会调用这里的函数了。 总之就是告诉xserver探测到了这个设备,并且使用了这个驱动来驱动这个设备。 如果遇到重复的设备,就不会被添加。注意kdrive指定设备文件只有一个地方,就是在命令行里面 有些驱动写的很烂,不支持hal,就需要命令行里面指定了,比如 -mouse,xorg则比较灵活 可以用命令行-pointer或者使用hal来探测,或者使用配置文件,反正就是方法比较多了。 添加了设备之后就可以使用了,不同的xserver添加方式不一样 xorg是这样的 xf86NewInputDevice 然后就会调用设备驱动里面的函数了,比如PreInit pInfo = drv->PreInit(drv, idev, 0); 比如 drv->UnInit(drv, pInfo, 0); 比如 xf86ActivateDevice ActivateDevice 激活设备的时候就会调用驱动里面的init函数了。 在dix/device.c里面,都是通用的了。 ret = (*dev->deviceProc) (dev, DEVICE_INIT); 当要关闭设备的时候xserver就会调用 CloseDevice然后就跑到驱动里面去关闭设备了,也是一个回调。 (void)(*dev->deviceProc)(dev, DEVICE_CLOSE); 这个deviceProc实际是设备相关的,如果是鼠标就是鼠标,如果是键盘就是键盘。 对于xorg来说。 都是在添加设备的时候指定的,比如pointer,就是鼠标了,我们可以这么理解。 pointer = AddInputDevice(client, CorePointerProc, TRUE); 这个proc函数其实比较简单了。 static int CorePointerProc(DeviceIntPtr pDev, int what) { BYTE map[33]; int i = 0; ClassesPtr classes; switch (what) { case DEVICE_INIT: if (!(classes = xcalloc(1, sizeof(ClassesRec)))) return BadAlloc; for (i = 1; i <= 32; i++) map[i] = i; InitPointerDeviceStruct((DevicePtr)pDev, map, 32, (PtrCtrlProcPtr)NoopDDA, GetMotionHistorySize(), 2); pDev->valuator->axisVal[0] = screenInfo.screens[0]->width / 2; pDev->last.valuators[0] = pDev->valuator->axisVal[0]; pDev->valuator->axisVal[1] = screenInfo.screens[0]->height / 2; pDev->last.valuators[1] = pDev->valuator->axisVal[1]; break; case DEVICE_CLOSE: break; default: break; } return Success; } 注意最新的xserver必须要有 InitPointerDeviceStruct 没有valuator的鼠标是不会被使用的,在汇报事件之后计算valuator会发生错误,然后事件就不会进到队列里面。 自己写鼠标驱动如果出问题一般都是这里出问题,当然不同鼠标有不同特性,可能也是鼠标驱动开发的一个问题。 关于valuator,比较常见的错误就是鼠标不能使用,或者bad valuator导致xserver崩溃,或者out-of-order的错误,或者是event queue溢出。 不过自己debug一下,或者使用ErrorF很容易看到问题出在什么地方。 好了,xorg的汇报事件的方法比较简单,如果像看驱动这里,evdev的驱动将来应该会一统天下,但是目前触摸拼还是有问题的。 evdev是将来linux平台标准的input驱动框架,至少我个人是这么理解的。好处比较多了, evdev就是event device了,可以是鼠标键盘或者其他的设备。都遵循标准了,如果debug,最好的工具摸过evbug这个内核模块。 如果evbug出来的数据就不对,说明问题肯定不在上层,在下面的驱动就出错了。 另外要说说的就是touchscreen的driver,本来有xf86-input-tslib,kdrive里面也是移植的这个, 这个东西的框架实际上是这样的 evdev->tslib->xf86-input-tslib tslib这边可以做一些过滤,平滑等等的处理,包括校正,不过校正不能在xserver运行过程中起作用。所以校正完毕之后需要重新启动xserver,加载tslib的驱动, 然后就可以正常使用了,本来有Xcaliberate的扩展,不过目前只能在kdrive上面使用,不知道xorg以后会不会有。触摸屏的pc太少了,没人维护。 evdev的caliberate实际上很不好用。 另外就是键盘实际上比鼠标的处理要复杂的多了。 因为键盘涉及到国际化的问题,比如键盘的layout,比如键盘的按键个数不同,语言不同,还有各种不同的metakey。或者组合键。 总之是很麻烦的事情,之前解决不少这样的问题。xserver这边有xkb来处理,不过这个东西并不是那么好用的。配置起来也不太顺手。 注意xkb的处理是必须配合使用的,比如xserver这边打开了xkb,并且设置了xkb的话,上层的应用程序必须知道这个,不然就会出问题,比如gdk这边的键值不对。 或者发现windowmanager的快捷键不起作用,或者发现按“a"出"b"这样的情况。一般来说都和xkb有关。 不知道还有什么漏掉的东西,以后慢慢补充吧,不过文章一放,肯定不会再写了, 主要打字还是很累的:) |
|