Essential Linux Device Drivers (翻译)第七章

翻译并不是一字一句的翻译,或者有的专业术语还会保持原样,尽可能准确的表达原文的意思。水平有限,敬请谅解。

 第7章:Input Drivers

内核的输入子系统的创建是为了统一分散的驱动程序处理的不同的类输入设备的,如键盘,鼠标,轨迹球,操纵杆,滚轮,触摸屏,加速度计,和平板电脑。输入子系统带来以下优点:

1.统一处理物理上不同但是功能上类似的输入设备,所有的鼠标,PS/2,USB,蓝牙,都被类似的对待;

2.一个简单的事件接口用来上报输入的数据到用户空间中,你的驱动不需要创建/dev下的节点以及访问的方法,相反,可以简单的调用input 的api函数来上报鼠标的移动,按键的按下,触摸事件到用户空间,应用如x  Windows 可以无缝的工作在输入子系统导出的事件接口。

3.提取了输入驱动的相同点产生了一个抽象的概念使得变得一致,例如,输入子系统提供了一个收集被称作serio的低阶驱动,例如串口,按键控制器。

图片7.1阐明了输入子系统是如何运作的,输入子系统有两类的驱动工作,event(事件) 驱动以及device(设备)驱动,event驱动是面向应用层,然而,device 驱动是面向低阶输入设备。鼠标事件发生器即mousedev是前者的例子,而PS/2驱动是后者,这两个事件的驱动程序和设备驱动程序可以利用一种有效的,无缺陷,可重复使用的核心,对于输入子系统来讲就是心脏。

  Figure 7.1. The input subsystem.

因为所有的event 驱动是通用的,所以一般是是实现device 驱动而不是event驱动,你的device 驱动可以选择一个合适的事件驱动通过输入子系统核心与用户空间建立联系。

Input Event Drivers

输入子系统导出的接口可以被许多图形界面系统所认识,事件驱动程序提供了一个独立于硬件的抽象去跟输入设备交流,类似于frame buffer的接口代表着一种机制与显示设备通信,event驱动和frame buffer驱动使得用户图形的接口与实际的硬件多样的变化分离出来了。

The Evdev Interface
evdev 是通用的事件驱动,evdev产生的包有如下的格式,在 include/linux/input.h中定义的:
struct input_event {
  struct timeval time;  /* Timestamp */
  __u16 type;           /* Event Type */
  __u16 code;           /* Event Code */
  __s32 value;          /* Event Value */
};
为了学习如何使用evdev,让我们一起实现一个虚拟的mouce 的device 驱动。
Device Example: Virtual Mouse
Virtual Mouse 的驱动是这样工作的,应用程序(coord.c)仿真鼠标移动并且分发坐标信息给virtual mouse的驱动程序(vms.c)通过sysfs节点,/sys/devices/platform/vms/coordinates,virtual mouse的驱动输送这些数据流通过evdev,图7.2是详细说明
General-purpose mouse ( gpm)是一个服务可以使你在使用鼠标的时候以文本形式使用,不需要借助图形界面,Gpm能够理解evdev的事件消息,因此vms驱动可以直接和Gpm通信,之后的一切地方,你可以看到光标在你的屏幕上舞蹈和你的coord.c中产生的一致。
list 7是coord.c,持续的产生x,y坐标,鼠标,不像是游戏杆和触摸屏,产生相对的坐标,这个就是coord.c所干的,
Listing 7.1. Application to Simulate Mouse Movements (coord.c)
#include <fcntl.h>

int
main(int argc, char *argv[])
{
  int sim_fd;
  int x, y;
  char buffer[10];

  /* Open the sysfs coordinate node */
  sim_fd = open("/sys/devices/platform/vms/coordinates", O_RDWR);
  if (sim_fd < 0) {
    perror("Couldn't open vms coordinate file\n");
    exit(-1);
  }
  while (1) {
    /* Generate random relative coordinates */
    x = random()%20;
    y = random()%20;
    if (x%2) x = -x; if (y%2) y = -y;

    /* Convey simulated coordinates to the virtual mouse driver */
    sprintf(buffer, "%d %d %d", x, y, 0);
    write(sim_fd, buffer, strlen(buffer));
    fsync(sim_fd);
    sleep(1);
  }

  close(sim_fd);
}
Listing 7.2. Input Driver for the Virtual Mouse (vms.c)
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/pci.h>
#include <linux/input.h>
#include <linux/platform_device.h>

struct input_dev *vms_input_dev;        /* Representation of an input device */
static struct platform_device *vms_dev; /* Device structure */

                                        /* Sysfs method to input simulated
                                           coordinates to the virtual
                                           mouse driver */
static ssize_t
write_vms(struct device *dev,
          struct device_attribute *attr,
          const char *buffer, size_t count)
{
  int x,y;
  sscanf(buffer, "%d%d", &x, &y);
                                        /* Report relative coordinates via the
                                           event interface */
  input_report_rel(vms_input_dev, REL_X, x);
  input_report_rel(vms_input_dev, REL_Y, y);
  input_sync(vms_input_dev);

  return count;
}

/* Attach the sysfs write method */
DEVICE_ATTR(coordinates, 0644, NULL, write_vms);

/* Attribute Descriptor */
static struct attribute *vms_attrs[] = {
  &dev_attr_coordinates.attr,
  NULL
};

/* Attribute group */
static struct attribute_group vms_attr_group = {
  .attrs = vms_attrs,
};

/* Driver Initialization */
int __init
vms_init(void)
{

  /* Register a platform device */
  vms_dev = platform_device_register_simple("vms", -1, NULL, 0);
  if (IS_ERR(vms_dev)) {
    PTR_ERR(vms_dev);
    printk("vms_init: error\n");
  }

  /* Create a sysfs node to read simulated coordinates */
  sysfs_create_group(&vms_dev->dev.kobj, &vms_attr_group);

  /* Allocate an input device data structure */
  vms_input_dev = input_allocate_device();
  if (!vms_input_dev) {
    printk("Bad input_alloc_device()\n");
  }

  /* Announce that the virtual mouse will generate
     relative coordinates */
  set_bit(EV_REL, vms_input_dev->evbit);
  set_bit(REL_X, vms_input_dev->relbit);
  set_bit(REL_Y, vms_input_dev->relbit);

  /* Register with the input subsystem */
  input_register_device(vms_input_dev);

  printk("Virtual Mouse Driver Initialized.\n");
  return 0;
}

/* Driver Exit */
void
vms_cleanup(void)
{

  /* Unregister from the input subsystem */
  input_unregister_device(vms_input_dev);

  /* Cleanup sysfs node */
  sysfs_remove_group(&vms_dev->dev.kobj, &vms_attr_group);

  /* Unregister driver */
  platform_device_unregister(vms_dev);

  return;
}

module_init(vms_init);
module_exit(vms_cleanup);
让我们走近去看一下listing 7.2,初始化过程中,vms的驱动注册自己为input设备驱动,首先申请了一个input_dev 的结构体使用api函数 input_allocate_device():
vms_input_dev = input_allocate_device();

然后宣布虚拟鼠标生成相对的事件

set_bit(EV_REL, vms_input_dev->evbit);  /* Event Type is EV_REL */
接下来,它声明虚拟鼠标产生的事件编码

set_bit(REL_X, vms_input_dev->relbit); /* Relative 'X' movement */
set_bit(REL_Y, vms_input_dev->relbit); /* Relative 'Y' movement */
如果你的虚拟鼠标也能生成按钮点击,您需要添加这个vms_init():

set_bit(EV_KEY, vms_input_dev->evbit);  /* Event Type is EV_KEY */
set_bit(BTN_0,  vms_input_dev->keybit); /* Event Code is BTN_0 */
最后,注册:

input_register_device(vms_input_dev);
write_vms()是sysfs的store()方法连接到/sys/devices/platform/vms/coordinates,当coord.c写一个X / Y这个文件write_vms()做以下事情:

input_report_rel(vms_input_dev, REL_X, x);
input_report_rel(vms_input_dev, REL_Y, y);
input_sync(vms_input_dev);
第一句产生一个 REL_X的事件相对于X方向上的,第二句产生一个REL_的事件相对于Y方向上的,input_sync()表明事件完成,因此input子系统收集到两个事件之后转化为简单的evdev的数据包发送到/dev/input/eventX,X是分配给vms_driver的接口编号,应用读取这个文件时会收到event的数据包以input_event格式 ,看到光标在屏幕上能够随着上报的值动:

bash> gpm -m /dev/input/eventX -t evdev

ADS7846触摸控制器驱动和加速度计的驱动,也是使用evdev的。

More Event Interfaces
vms 的驱动利用evdev的事件接口,但是输入设备例如触摸板,按键有自定义的事件驱动。
写你自己的事件驱动并且导出到用户空间 /dev/input/mydev/,你需要构造一个一个叫做 input_handler的结构体并且注册他们使用input-core。
static struct input_handler my_event_handler = {
  .event      = mydev_event,      /* Handle event reports sent by
                                     input device drivers that use
                                     this event driver's services */
  .fops       = &mydev_fops,      /* Methods to manage
                                     /dev/input/mydev */
  .minor      = MYDEV_MINOR_BASE, /* Minor number of
                                     /dev/input/mydev */
  .name       = "mydev",          /* Event driver name */
  .id_table   = mydev_ids,        /* This event driver can handle
                                     requests from these IDs */
  .connect    = mydev_connect,    /* Invoked if there is an
                                     ID match */
  .disconnect = mydev_disconnect, /* Called when the driver unregisters
                                   */
};

/* Driver Initialization */
static int __init
mydev_init(void)
{
  /* ... */

  input_register_handler(&my_event_handler);

  /* ... */
  return 0;
}

看实现mousedev( drivers/input/mousedev.c)一个完整的示例

Input Device Drivers

让我的视线回到常用的输入设备中,例如键盘,鼠标,以及触摸屏。但是首先,先快速看一下现成的访问设备可用的输入驱动程序。
Serio
serio层提供了库程序去访问老旧的输入设备例如i8042兼容的键盘控制器以及串口,PS/2键盘和鼠标是前者,而串行的触摸控制器连接的是后者,为了与serio提供的服务的硬件通信,例如发送命令给一个PS/2的鼠标,注册规定的程序使用serio的函数 serio_register_driver().
添加一个新去驱动成为serio的一部分,注册 open()/close()/start()/stop()/write()入口点使用serio_register_port (),查看drivers/input/serio/serport.c。
如图7.1中所示,serio仅仅是访问硬件设备的一种方法,几个输入驱动程序仅仅是依靠总线层的支持,例如USB和SPI。
Keyboards
键盘是来自PS/2,USB,Bluetooth, Infrared(红外)的产物,每一种类型都有各自特点的驱动程序,但是使用相同的键盘事件驱动,保证对于他们的使用者拥有一致的接口,按键事件驱动,仍然有与其他的事件驱动不同的地方,它传递数据到另一个内核子系统(tty layer)而不是通过/dev 节点。
PC Keyboards
PC键盘使用i8042兼容的控制器,桌面电脑一般有专有的控制器,但是笔记本仅仅是通用意义上的嵌入式控制器,当你的键按下是,会反生以下的事情:
1.按键控制器扫描键盘矩阵,消除抖动。
2.键盘驱动程序,在serio驱动程序的帮助下,读取到扫描值,包括每一个键的按下和释放,按下和释放一般是在是在最高位区分,按下‘a’,会产生一对值 0x1e和0x9e,特殊键使用取0xE0,当敲击在->按键产生一个序列(0xE0 0x4D 0xE0 0xCD,您可以使用showkey测试工具来观察扫描码从控制器发出
  1. bash> showkey -s
    kb mode was UNICODE
    [ if you are trying this under X, it might not work since
     the X server is also reading /dev/console ]
    
     press any key (program terminates 10s after last
     keypress)...
     ...
     0x1e 0x9e rightwards double arrow A push of the "a" key
3.键盘驱动程序转换收到的键值,去查看’a‘的键值

  1. bash> showkey
    ...
    keycode 30 press   rightwards double arrow A push of the "a" key
    keycode 30 release rightwards double arrow Release of the "a" key
为了将键值上报,产生一个input的事件。
4.键盘事件驱动程序进行键码翻译根据加载按键键值表,键值表在/lib/kpd/keymaps/,查看键值是否要开关,重启。点 亮 CAPSLOCKNUMLOCK的led灯替换Ctrl+Alt+Del按下重启系统,增加按键事件驱动代码中在drivers/char/keyboard.c
static void fn_boot_it(struct vc_data *vc,

                       struct pt_regs *regs)
{
+  set_vc_kbd_led(kbd, VC_CAPSLOCK);
+  set_vc_kbd_led(kbd, VC_NUMLOCK);
-  ctrl_alt_del();
}
5.对于通用的按键,导出的键值发送到相关的虚拟终端上和N_TTY线路规程上,这个由drivers/char/keyboard.c完成
/* Add the keycode to flip buffer */
tty_insert_flip_char(tty, keycode, 0);
/* Schedule */
con_schedule_flip(tty);
N_TTY线路规程处理从键盘输入来的数据,与虚拟控制台呼应,使用户空间应用程序读取字符通过连接到虚拟终端上的 /dev/ttyX 节点。

图7.3表示了你按下键的时刻直到反射到终端上的时刻,左边的图是硬件相关的,右半部分是通用的,作为每个输入子系统设计的目标,处于下游的硬件对应事件驱动和tty驱动是易于理解的,输入核心input-core清晰的定义了事件接口因此分离了用户及硬件。


USB and Bluetooth Keyboards

USB规范相关人机接口设备规定了用来通信的USB键盘,鼠标,其他的输入外设的协议。在linux中,这个通过usbhid client 驱动来实现,这个驱动对USB HID 类(0x03)负责的,Usbhid的寄存器自己作为了输入设备驱动,其符合输入子系统的api,上报连接在HID合适的事件。

为了理解USB键盘的代码路径,查看7.3改变左半部分的硬件相关的,替换在Input Hardware方框键盘控制器为USB控制器,输入设备用usbhid驱动替换。

对于蓝牙键盘来说,替换图7.3中的键盘控制器为蓝牙芯片组,serio使用蓝牙核心层户,输入设备使用蓝牙hidp驱动替换。

USB和蓝牙在11章中讨论。

Mice
鼠标,类似于键盘,有不同的功能以及有不同的接口选项,让我们看看常见的。
PS/2 Mice
鼠标产生相对于x,y轴的相对坐标,有一个或者多个按键,一些有滚轮,输入设备驱动依赖于serio层与底层硬件通信,输入事件针对于鼠标是 mousedev,通过via/dev/input/mice向用户层传递消息。

Device Example: Roller Mouse
感受一个真实的鼠标设备驱动程序,让我们回过来看第4章讨论的滚轮,“奠定基础,”到通用PS / 2鼠标的变化。该“鼠标滚轮”,在Y轴产生一维运动。顺时针和逆时针分别产生正,负相对Y坐标,同时按下滚轮导致左键鼠标事件。鼠标滚轮因而非常适用于诸如智能手机,掌上电脑和音乐播放器等设备浏览菜单。

鼠标滚轮驱动在listing 7.3中实现,可于窗口系统如X视窗工作,看看roller_mouse_init(),看看该驱动程序声明了其鼠标般的功能。与列表第4章的4.1滚轮驱动,滚轮鼠标驱动程序,需要的不是read()或poll()方法,因为事件是使用输入API的。滚轮中断处理程序roller_isr()也相应变化,中断的开支使用了等待队列,原子锁, store_movement()程序支持read()和poll()。

Listing 7.3. The Roller Mouse Driver
+  #include <linux/input.h>
+  #include <linux/interrupt.h>

+  /* Device structure */
+  struct {
+    /* ... */
+    struct input_dev dev;
+  } roller_mouse;

+  static int __init
+  roller_mouse_init(void)
+  {
+  /* Allocate input device structure */
+  roller_mouse->dev = input_allocate_device();
+
+  /* Can generate a click and a relative movement */
+  roller_mouse->dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);

+  /* Can move only in the Y-axis */
+  roller_mouse->dev->relbit[0] = BIT(REL_Y);
+
+  /* My click should be construed as the left button
+     press of a mouse */
+  roller_mouse->dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT);

+  roller_mouse->dev->name = "roll";
+
+  /* For entries in /sys/class/input/inputX/id/ */
+  roller_mouse->dev->id.bustype = ROLLER_BUS;
+  roller_mouse->dev->id.vendor = ROLLER_VENDOR;
+  roller_mouse->dev->id.product = ROLLER_PROD;
+  roller_mouse->dev->id.version = ROLLER_VER;

+  /* Register with the input subsystem */
+  input_register_device(roller_mouse->dev);
+}

/* Global variables */
- spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;
- static DECLARE_WAIT_QUEUE_HEAD(roller_poll);

/* The Roller Interrupt Handler */
static irqreturn_t
roller_interrupt(int irq, void *dev_id)
{
  int i, PA_t, PA_delta_t, movement = 0;

  /* Get the waveforms from bits 0, 1 and 2
     of Port D as shown in Figure 7.1 */
  PA_t = PORTD & 0x07;

  /* Wait until the state of the pins change.
     (Add some timeout to the loop) */
  for (i=0; (PA_t==PA_delta_t); i++){
     PA_delta_t = PORTD & 0x07;
  }

  movement = determine_movement(PA_t, PA_delta_t);

- spin_lock(&roller_lock);
-
- /* Store the wheel movement in a buffer for
-    later access by the read()/poll() entry points */
- store_movements(movement);
-
- spin_unlock(&roller_lock);
-
- /* Wake up the poll entry point that might have
-    gone to sleep, waiting for a wheel movement */
- wake_up_interruptible(&roller_poll);
-
+ if (movement == CLOCKWISE) {
+   input_report_rel(roller_mouse->dev, REL_Y, 1);
+ } else if (movement == ANTICLOCKWISE) {
+   input_report_rel(roller_mouse->dev, REL_Y, -1);
+ } else if (movement == KEYPRESSED) {
+   input_report_key(roller_mouse->dev, BTN_LEFT, 1);
+ }
+ input_sync(roller_mouse->dev);

  return IRQ_HANDLED;
}

					  

Trackpoints
一个指点杆是自带的PS/2式键盘集成在一些笔记本电脑的定位设备。该装置包括位于定位在所述空格键下键和鼠标按钮之间的操纵杆。一个指点杆基本上可用作鼠标,让您可以使用PS/2鼠标驱动程序操作。
不同于常规的鼠标,指点杆提供了更多的运动控制。你可以命令指点杆控制人变更,如灵敏度和惯性特性。内核有一个特殊的驱动程序,驱动程序/输入/鼠标/ trackpoint.c,创建和管理相关的sysfs节点。对于全套航迹点的配置选项,在/ sys/device/platform/ i8042/ serioX/ serioY/
Touchpads
触摸板是一个鼠标式定点设备上的笔记本电脑中常见的。不同于传统的鼠标,触摸板不具有移动部件。它可以产生鼠标兼容相对坐标,但通常在产生绝对坐标一个更强大的模式使用的操作系统。在绝对模式中使用的通信协议是类似于PS / 2鼠标协议,但与它不兼容。
基本PS / 2鼠标驱动程序能够支持符合裸PS / 2鼠标协议的不同变化的设备。您可以通过通过psmouse结构提供的协议驱动程序添加一个新的鼠标协议的基础驱动程序的支持。如果你的笔记本使用Synaptics触摸板的绝对模式,例如,基PS/2鼠标驱动程序使用了Synaptics的协议驱动程序的服务来解释流数据。有关如何Synaptics的协议协同工作的基础PS/2驱动程序的终端到终端的理解,看看列表收集7.4以下四个代码区域
  • PS/2 鼠标驱动,drivers/input/mouse/psmouse-base.c,实例化一个psmouse_protocol结构体,信息包括鼠标协议(触摸板协议也包括)

  • psmouse结构体,定义在drivers/input/mouse/psmouse.h中,将PS/2的协议都绑定到一起。

  • synaptics_init()填充psmouse结构的相关协议功能的地址。

  • 协议处理函数synaptics_process_byte(),填充synaptics_init(),被从中断上下文调用时,Serio的感应鼠标移动。如果你展开synaptics_process_byte(),你会看到触控板走势,报经mousedev用户应用程序。

Listing 7.4. PS/2 Mouse Protocol Driver for the Synaptics Touchpad
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值