这个我们应该分成几大块来说吧,
kdrive和xorg的处理是很类似的。
kdrive的驱动都在这个目录下面
hw/kdrive/linux
大
家看到有键盘,鼠标,触摸屏,evdev等等的驱动。
其实说白了,在linux系统上面,驱动是分好几部分的,如果我们从最上层来看,我
们看到的是图形界面,也就是xserver,实际上xserver这里有这里的驱动层。
以input为例吧,我们看看这个地方。
这
个地方凡是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有关。
不
知道还有什么漏掉的东西,以后慢慢补充吧,不过文章一放,肯定不会再写了,
主要打字还是很累的:)
阅读(1798) | 评论(0) | 转发(1) |