linux设备驱动之8250串口驱动

合集下载

linux下i2c驱动以及与pcf8563通信(二)

linux下i2c驱动以及与pcf8563通信(二)

2012/7/10linux下i2c驱动与通信(二)by: 韩大卫@吉林师范大学转载请表明出处在i2c-test的基础上,使用i2c与Pcf8563通信,通过发送和接收数据,对rtc芯片进行set 和get操作。

既然是操作时间,可以使用struct rtc_time, 定义如下:struct rtc_time {int tm_sec;int tm_min;int tm_hour;int tm_mday;}在pcf8563_get_datetime()中:struct i2c_msg msgs[] = {{ client->addr, 0, 1, buf }, /* setup read ptr */{ client->addr, I2C_M_RD, 13, buf }, /* read status + date */};定义了两个msg, 在i2c-octeon 中分别进入了ret = octeon_i2c_simple_write()ret = octeon_i2c_read()进入simple_write()作用是将第一个buf 即offset 写入寄存器,这样在read函数即可直接读此寄存器。

2012.7.11 .14:50出现这样一个问题:root@juson:/han# ./i2c-teststrlen(buf) = 1buf[0] = 8root@juson:/han# ./i2c-test -rstrlen(buf) = 1buf[0] = 8root@juson:/han# ./i2c-test -l2012年07月11日星期三21:49:41root@juson:/han# ./i2c-teststrlen(buf) = 13buf[0] = 8buf[1] = 40buf[2] = 43buf[3] = 49buf[4] = 21buf[5] = 11buf[6] = 3buf[7] = 7buf[8] = 12buf[9] = a0buf[10] = 84buf[11] = b2buf[12] = b5有少两情况下会出现strlen(buf) = 13过一会:root@juson:/han# ./i2c-teststrlen(buf) = 13buf[0] = 8buf[1] = 40buf[2] = 54buf[3] = 53buf[4] = 21buf[5] = 11buf[6] = 3buf[7] = 7buf[8] = 12buf[9] = a0buf[10] = 84buf[11] = b2buf[12] = b5root@juson:/han# ./i2c-teststrlen(buf) = 13buf[0] = 8buf[1] = 40buf[2] = 55buf[3] = 53buf[4] = 21buf[5] = 11buf[6] = 3buf[7] = 7buf[8] = 12buf[9] = a0buf[10] = 84buf[11] = b2buf[12] = b5这次是全部是root@juson:/han# ./i2c-teststrlen(buf) =13 的情况。

Linux下的硬件驱动——USB设备

Linux下的硬件驱动——USB设备

Linux下的硬件驱动——USB设备什么是USB设备?USB即Universal Serial Bus,翻译过来就是通用串行总线。

它是一种规范化的、快速的、热插拔的串行输入/输出接口。

USB接口常被用于连接鼠标、键盘、打印机、扫描仪、音频设备、存储设备等外围设备。

Linux下的USB驱动在Linux系统中,每个USB设备都需要一个相应的驱动程序来驱动。

从Linux 2.4开始,内核提供了完整的USB设备支持。

对于每个USB设备,内核都会自动加载对应的驱动程序。

Linux下的USB设备驱动程序主要分为以下几个部分:USB核心驱动程序USB核心驱动程序是操作系统内核中处理USB设备的核心模块,负责与各种类型的USB设备进行通信,包括主机控制器、USB总线、USB设备等。

它与驱动程序和应用程序之间起到了桥梁的作用,为驱动程序提供了USB设备的基础支持。

USB设备驱动程序USB设备驱动程序是与特定USB设备相对应的驱动程序,为USB设备提供具体的读写功能和其他控制功能。

USB核心驱动程序和USB设备驱动程序之间的接口USB核心驱动程序和USB设备驱动程序之间的接口是指USB层和应用程序层之间的接口,负责传递各种USB操作的命令和数据。

如何编译一个USB设备驱动编译一个USB设备驱动程序需要按照以下步骤进行:步骤一:安装必要的软件包首先需要安装编译和调试USB设备驱动所需的软件包,包括编译工具链、内核源代码、内核头文件等。

sudo apt-get install build-essential linux-source linux-headers-`una me -r`步骤二:编写代码现在可以编写USB设备驱动程序的代码,此处不做详细介绍。

步骤三:编译代码在终端窗口中进入USB设备驱动程序所在的目录下,输入以下命令进行编译:make此命令将会编译USB设备驱动程序,并生成一个将驱动程序与内核进行连接的模块文件。

linux设备驱动之8250串口驱动

linux设备驱动之8250串口驱动

linux设备驱动之8250串口驱动一:前言前一段时间自己实践了一下8250芯片串口驱动的编写。

今天就在此基础上分析一下linux kernel自带的串口驱动。

毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱动代码位于:linux-2.6.25/drivers/serial/8250.c。

二:8250串口驱动初始化相应的初始化函数为serial8250_init().代码如下:static int __init serial8250_init(void){int ret, i;if (nr_uarts > UART_NR)nr_uarts = UART_NR;printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ ""%d ports, IRQ sharing %sabled\n", nr_uarts,share_irqs ? "en" : "dis");for (i = 0; i < NR_IRQS; i++)spin_lock_init(&irq_lists[i].lock);ret = uart_register_driver(&serial8250_reg);if (ret)goto out;serial8250_isa_devs = platform_device_alloc("serial8250",PLA T8250_DEV_LEGACY);if (!serial8250_isa_devs) {ret = -ENOMEM;goto unreg_uart_drv;}ret = platform_device_add(serial8250_isa_devs);if (ret)goto put_dev;serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);ret = platform_driver_register(&serial8250_isa_driver);if (ret == 0)goto out;platform_device_del(serial8250_isa_devs);put_dev:platform_device_put(serial8250_isa_devs);unreg_uart_drv:uart_unregister_driver(&serial8250_reg);out:return ret;}这段代码涉及到的知识要求,如platform ,uart等我们在之前都已经做过详细的分析。

8250串口实验

8250串口实验

实验七 8250串口实验一、实验目的1、熟悉串行通信的一般原理和8250的工原理。

2、了解RS--232串行接口标准及连接方法。

3、掌握8250芯片的编程方法。

二、实验设备MUT—Ⅲ型实验箱、8086CPU模块。

三、实验原理介绍1.实验原理图见8250串行接口电路。

由MAX232完成RS232电平和TTL电平的转换,由8250完成数据的收发。

8250内部有10个寄存器,分别对应着不同的IO口地址。

对不同的寄存器进行初始化或读出写入操作就可以完成与计算机的通信。

由于不能同时收发数据,所以8250又称为通用串行异步收发器,简写为:UART。

8250实验电路的所有信号均已连好。

8250串行接口电路(1)电路原理:该电路由一片8250,一片MAX232组成,该电路所有信号线均已接好。

原理图如下:(2)电路测试:见整机测试2.程序框图(8250.ASM)3.程序代码;*******************************************code segment ;define data segmentassume cs:codeorg 0100hstart:mov bx,0480hmov dx,bxadd dx,6mov ax,80hout dx,axmov dx,bxmov ax,0ch ;000ch---9600 ,clk=4.77MHZ/4 ; AL=4770000/16/9600/4=8out dx,axadd dx,2mov ax,0hout dx,axadd dx,4mov ax,07 ;no pe,8 bit, 1 stopout dx,axmov dx,bxadd dx,2 ;no interuptmov ax,0out dx,axadd dx,8hin ax,dxmov dx,bxin ax,dxcrd: call recvcall sendjmp crdsend: push axmov bx,0480hmov dx,bxadd dx,0ahin ax,dxtest ax,20hjnz recv2pop axjmp sendrecv2: pop axmov dx,bxout dx,axretrecv: mov bx,0480hmov dx,bxadd dx,0ahin ax,dxtest ax,01hjnz recv1jmp recvrecv1: mov dx,bxin ax,dxretcode ends ;end of code segmentend start ;end assembly3.实验提示实验中,通讯波特率选用9600bps。

基于MPC8250的嵌入式Linux系统开发

基于MPC8250的嵌入式Linux系统开发

基于MPC8250的嵌入式Linux系统开发引言我们开发了某型飞机外场维护系统的一个网络终端通讯平台,其硬件核心采用Motorola公司的嵌入式PowerPC处理器MPC8250,软件方面采用嵌入式linux作为操作系统,实现了飞机外场维护与飞机故障诊断系统的安全、快速通信。

这个系统平台具有体积小、功耗低、性能高等特点。

MPC8250构架与系统硬件平台简介PowerPC是Linux较早开始支持的处理器之一。

PowerPC处理器芯片MPC8250基于PowerQUICC(Quad Integrated CommunicationsController)II 结构,是面向高性能、低功耗、小体积的通信设备而开发的处理器。

其内部集成了一个高性能嵌入式G2 内核、一个灵活的系统集成单元SIU和许多控制领域的常用外围通信组件(组成通信处理模块CPM,CommunicationsProcessor Module),可用于许多方面,尤其是在通讯和网络系统方面。

G2内核频率在150-200MHz之间CPM、PCI最高频率为133MHz,外部频率为66MHz,可同时处理高达128个全双工时分复用逻辑通道。

根据目标系统的不同,系统硬件平台可以引出所需的MPC8250的外部通讯接口。

我们的系统提供了8M的flash用于存放内核镜像文件、文件系统、应用程序和备份数据,64MSDRAM用作内存,521KB 的flash用于存放启动代码,此外还有RS232串口、10/100M以太网口以及JTAG支持等。

基于MPC8250的嵌入式linux系统开发编译环境的定制通常主机与目标板的CPU都不相同,需要进行交叉编译。

能够进行交叉编译的工具很多,一般使用GNU C,它包括gcc、g++编译器,glibc、newlib 等C 库,binutils 以及其他一系列开发工具。

不同的开发平台需要不同的交叉编译工具。

这里我们选择德国denx软件中心提供的一套用于PowerPC嵌入式linux移植的开发编译环境ELDK3.0(Embedded LinuxDevelopmentKit)。

linux 模拟硬件拔插的命令

linux 模拟硬件拔插的命令

linux 模拟硬件拔插的命令Linux模拟硬件拔插的命令在日常的Linux系统管理和维护中,经常会遇到需要模拟硬件拔插的情景。

这些情景可能是为了测试硬件设备的可靠性、检测驱动程序的兼容性,亦或是进行系统的故障排查。

本文将一步一步地回答关于Linux模拟硬件拔插的命令,并提供一些实例来帮助读者更好地理解。

1. Linux设备模拟工具简介在Linux系统中,可以使用一些设备模拟工具来模拟硬件的插拔操作。

这些工具可以模拟多种硬件设备,如USB设备、网卡、串口等,并提供与真实硬件设备一样的操作体验。

下面我们将介绍一些常用的设备模拟工具。

1.1 USB\_GADGETUSB\_GADGET是一个支持模拟USB设备的框架,可以用于在Linux设备上模拟USB设备插拔的操作。

它通过创建一个虚拟设备并将其绑定到相应的USB总线上来实现设备模拟。

使用USB\_GADGET需要在内核配置文件中启用相应的选项,并在系统启动时加载相应的内核模块。

一旦虚拟设备创建成功,可以通过向特定的配置文件写入命令来模拟设备的插拔。

1.2 EEMulatorEEMulator是一个开源的USB模拟器,支持模拟多种USB设备,如鼠标、键盘、摄像头等。

它可以通过虚拟机、容器、实体主机等多种方式运行,并提供与真实设备相同的输入输出接口。

EEMulator可以帮助开发人员测试其软件的USB设备兼容性,并提供了丰富的API和命令行工具以便进行设备模拟。

1.3 QEMUQEMU是一款开源的虚拟机监控器,可以模拟多种硬件平台的设备。

它支持通过命令行或图形界面进行设备模拟,并可模拟多种虚拟硬件设备,如网卡、声卡、USB等。

QEMU提供了灵活的配置选项,可以根据需要来进行设备模拟,并支持多种虚拟硬盘和光驱格式。

2. 使用USB\_GADGET模拟硬件插拔USB\_GADGET是一种内核模块,可用于模拟USB设备的插拔操作。

以下是一些使用USB\_GADGET模拟硬件插拔的实例。

linux下的串口通信原理及编程实例

linux下的串口通信原理及编程实例

linux下的串⼝通信原理及编程实例linux下的串⼝通信原理及编程实例⼀、串⼝的基本原理1 串⼝通讯串⼝通讯(Serial Communication),是指外设和计算机间,通过数据信号线、地线等,按位进⾏传输数据的⼀种通讯⽅式。

串⼝是⼀种接⼝标准,它规定了接⼝的电⽓标准,没有规定接⼝插件电缆以及使⽤的协议。

2 串⼝通讯的数据格式 ⼀个字符⼀个字符地传输,每个字符⼀位⼀位地传输,并且传输⼀个字符时,总是以“起始位”开始,以“停⽌位”结束,字符之间没有固定的时间间隔要求。

每⼀个字符的前⾯都有⼀位起始位(低电平),字符本⾝由7位数据位组成,接着字符后⾯是⼀位校验位(检验位可以是奇校验、偶校验或⽆校验位),最后是⼀位或⼀位半或⼆位停⽌位,停⽌位后⾯是不定长的空闲位,停⽌位和空闲位都规定为⾼电平。

实际传输时每⼀位的信号宽度与波特率有关,波特率越⾼,宽度越⼩,在进⾏传输之前,双⽅⼀定要使⽤同⼀个波特率设置。

3 通讯⽅式单⼯模式(Simplex Communication)的数据传输是单向的。

通信双⽅中,⼀⽅固定为发送端,⼀⽅则固定为接收端。

信息只能沿⼀个⽅向传输,使⽤⼀根传输线。

半双⼯模式(Half Duplex)通信使⽤同⼀根传输线,既可以发送数据⼜可以接收数据,但不能同时进⾏发送和接收。

数据传输允许数据在两个⽅向上传输,但是,在任何时刻只能由其中的⼀⽅发送数据,另⼀⽅接收数据。

因此半双⼯模式既可以使⽤⼀条数据线,也可以使⽤两条数据线。

半双⼯通信中每端需有⼀个收发切换电⼦开关,通过切换来决定数据向哪个⽅向传输。

因为有切换,所以会产⽣时间延迟,信息传输效率低些。

全双⼯模式(Full Duplex)通信允许数据同时在两个⽅向上传输。

因此,全双⼯通信是两个单⼯通信⽅式的结合,它要求发送设备和接收设备都有独⽴的接收和发送能⼒。

在全双⼯模式中,每⼀端都有发送器和接收器,有两条传输线,信息传输效率⾼。

显然,在其它参数都⼀样的情况下,全双⼯⽐半双⼯传输速度要快,效率要⾼。

串口驱动程序的编写总结(一)

串口驱动程序的编写总结(一)

串⼝驱动程序的编写总结(⼀)8250/16450/16550芯⽚都⽤同个8250驱动1、对现有驱动进⾏拷贝,然后进⾏局部修改2、不必过多深⼊系统内核驱动的调⽤过程,区分好哪些是需要修改的,哪些是内核驱动⾃带的3、对于要修改的内容,参考别⼈成功的例⼦,看哪些需要修改的4、必要时,可以先把原拷贝先不加载进驱动,把⾃⼰拷贝的驱动加载进去5、谨记要实现的功能,按步骤实现6、知道每个模块的作⽤与功能,哪些是涉及硬件,哪些是涉及系统的,⼀般来说,进⾏设备、驱动的注册时,⼀般不涉及驱动,只有应⽤层调⽤时才进⾏硬件的相关调⽤。

7、对串⼝驱动程序的改造时如果是采⽤外部模块加载的⽅式,即insmod⽅式,⽽不是内置于内核⽣成vmlinux,则不能使⽤console驱动,否则编译会出现error: redefinition of '__inittest'/opt/kangear/hello/hello.c:16: note: previous definition of '__inittest' was here错误,会出现重定义的情况。

解决⽅法:去除console的相关驱动,屏蔽console_initcall()函数的调⽤8、对串⼝的发送的配置属性,最终调⽤底层驱动的ioctl函数。

⽽ioctl函数得执⾏copy_from_user、copy_to_user函数进⾏⽤户与内核之间的数据拷贝,⽽在ioctl函数执⾏这些操作后,底层的驱动程序才能继续对配置参数(波特率、数据位、停⽌位、检9、在⽤户层⾯操作open()函数时,会调⽤底层驱动的⼀系列默认配置参数,这是在uart_core.c⽂件⾥进⾏属性的配置10、中断有分系统中断与外部中断,系统中断在⼀开机时就已经初始好,⽽外部中断是在驱动程序启动时调⽤,⽽中断的触发是靠硬件进⾏中断请求,cpu响应进⾏处理驱动详解:1、在串⼝驱动中,中断的产⽣都是⽤户态所触发引起的。

linux设备驱动(27)usb驱动-热插拔详解

linux设备驱动(27)usb驱动-热插拔详解

linux设备驱动(27)usb驱动-热插拔详解1 热插拔的基本概念1.1 usb热插拔的硬件原理在USB集线器(hub)的每个下游端⼝的D+和D-上,分别接了⼀个15K欧姆的下拉电阻到地。

这样,在集线器的端⼝悬空时,就被这两个下拉电阻拉到了低电平。

⽽在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。

对于全速和⾼速设备,上拉电阻是接在D+上;⽽低速设备则是上拉电阻接在D-上。

这样,当设备插⼊到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的⼀条拉⾼了。

集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上⼀层的集线器报告给USB主控制器),这样就检测到设备的插⼊了。

USB⾼速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到⾼速模式的。

在⾼速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

1.2 热插拔的概念热插拔(hot-plugging或Hot Swap)即带电插拔,热插拔功能就是允许⽤户在不关闭系统,不切断电源的情况下取出和更换损坏的硬盘、电源或板卡等部件,从⽽提⾼了系统对灾难的及时恢复能⼒、扩展性和灵活性等,例如⼀些⾯向⾼端应⽤的磁盘镜像系统都可以提供磁盘的热插拔功能。

具体⽤学术的说法就是:热替换(Hot replacement)、热添加(hot expansion)和热升级(hot upgrade)1.3 热插拔的优点在系统开机情况下将损坏的模块移除,还可以在开机情况下做更新或扩容⽽不影响系统操作。

由于热插拔零件的可靠度提升,还可以将它们⽤做断电器,⽽且因为热插拔能够⾃动恢复,有很多热插拔芯⽚为系统提供线路供电情况的信号,以便系统做故障分析,因此减少了成本。

2 热插拔的实现2.1 Linux下USB HUB的驱动的实现和分析:在系统初始化的时候在usb_init函数中调⽤usb_hub_init函数,就进⼊了hub的初始化。

Linux设备驱动之HID驱动---非常全面而且深刻

Linux设备驱动之HID驱动---非常全面而且深刻

Linux设备驱动之HID驱动---⾮常全⾯⽽且深刻本⽂系本站原创,欢迎转载!转载请注明出处:/------------------------------------------⼀:前⾔继前⾯分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做⼀个具体的USB设备驱动分析的例⼦.HID是Human Interface Devices的缩写.翻译成中⽂即为⼈机交互设备.这⾥的⼈机交互设备是⼀个宏观上⾯的概念,任何设备,只要符合HID spec,都⼆:HID驱动⼊⼝分析USB HID设备驱动⼊⼝位于linux-2.6.25/drivers/hid/usbhid/hid-core.c中.该module的⼊⼝为hid_init().代码如下:static int __init hid_init(void){int retval;retval = usbhid_quirks_init(quirks_param);if (retval)goto usbhid_quirks_init_fail;retval = hiddev_init();if (retval)goto hiddev_init_fail;retval = usb_register(&hid_driver);if (retval)goto usb_register_fail;info(DRIVER_VERSION ":" DRIVER_DESC);return0;usb_register_fail:hiddev_exit();hiddev_init_fail:usbhid_quirks_exit();usbhid_quirks_init_fail:return retval;}⾸先来看usbhid_quirks_init()函数.quirks我们在分析UHCI和HUB的时候也接触过,表⽰需要做某种修正的设备.该函数调⽤的参数是quirks_param.定义如下:static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };module_param_array_named(quirks, quirks_param, charp, NULL, 0444);从此可以看出, quirks_param是MAX_USBHID_BOOT_QUIRKS元素的字符串数组.并且在加载module的时候,可以动态的指定这些值.分析到这⾥.有⼈可以反应过来了,usbhid_quirks_init()是⼀种动态进⾏HID设备修正的⽅式.具体要修正哪些设备,要修正设备的那些⽅⾯,都可以由加载模块是所带参数来决定.usbhid_quirks_init()的代码如下:int usbhid_quirks_init(char **quirks_param){u16 idVendor, idProduct;u32 quirks;int n = 0, m;for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",&idVendor, &idProduct, &quirks);if (m != 3 ||usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {printk(KERN_WARNING"Could not parse HID quirk module param %s\n",quirks_param[n]);}}return0;}由此可以看出, quirks_param数组中的每⼀项可以分为三个部份,分别是要修正设备的VendorID,ProductID和要修正的功能.⽐如0x1000 0x0001 0x0004就表⽰:要忽略掉VendorID为0x1000,ProductID为0x0004的设备.(在代码中,有#define HID_QUIRK_跟进usbhid_modify_dquirk()函数,代码如下:int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,const u32 quirks){struct quirks_list_struct *q_new, *q;int list_edited = 0;if (!idVendor) {dbg_hid("Cannot add a quirk with idVendor = 0\n");return -EINVAL;}q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);if (!q_new) {dbg_hid("Could not allocate quirks_list_struct\n");return -ENOMEM;}q_new->hid_bl_item.idVendor = idVendor;q_new->hid_bl_item.idProduct = idProduct;q_new->hid_bl_item.quirks = quirks;down_write(&dquirks_rwsem);list_for_each_entry(q, &dquirks_list, node) {if (q->hid_bl_item.idVendor == idVendor &&q->hid_bl_item.idProduct == idProduct) {list_replace(&q->node, &q_new->node);kfree(q);list_edited = 1;break;}}if (!list_edited)list_add_tail(&q_new->node, &dquirks_list);up_write(&dquirks_rwsem);return0;}这个函数⽐较简单,就把quirks_param数组项中的三个部份存⼊⼀个封装结构.然后将其结构挂载到dquirks_list表.如果dquirks_list有重复的VendorId和ProductID就更新其quirks信息.经过usbhid_quirks_init()之后,所有要修正的设备的相关操作都会存放在dquirks_list中.返回到hid_init(),继续往下⾯分析.hiddev_init()是⼀个⽆关的操作,不会影响到后⾯的操作.忽略后⾯就是我们今天要分析的重点了,如下:retval = usb_register(&hid_driver);通过前⾯对HUB的驱动分析,相信对usb_redister()应该很熟悉了.hid_driver定义如下:static struct usb_driver hid_driver = {.name = "usbhid",.probe = hid_probe,.disconnect = hid_disconnect,.suspend = hid_suspend,.resume = hid_resume,.reset_resume = hid_post_reset,.pre_reset = hid_pre_reset,.post_reset = hid_post_reset,.id_table = hid_usb_ids,.supports_autosuspend = 1,};其中,id_table的结构为hid_usb_ids.定义如下:static struct usb_device_id hid_usb_ids [] = {{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,.bInterfaceClass = USB_INTERFACE_CLASS_HID },{ } /* Terminating entry */};也就是说,该驱动会匹配interface的ClassID,所有ClassID为USB_INTERFACE_CLASS_HID的设备都会被这个驱动所匹配.所以,所有USB HID设备都会由这个module来驱动.三:HID驱动的probe过程从上⾯的分析可看到,probe接⼝为hid_probe().定义如下:static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id){struct hid_device *hid;char path[64];int i;char *c;dbg_hid("HID probe called for ifnum %d\n",intf->altsetting->desc.bInterfaceNumber);//config the hid deviceif (!(hid = usb_hid_configure(intf)))return -ENODEV;usbhid_init_reports(hid);hid_dump_device(hid);if (hid->quirks & HID_QUIRK_RESET_LEDS)usbhid_set_leds(hid);if (!hidinput_connect(hid))hid->claimed |= HID_CLAIMED_INPUT;if (!hiddev_connect(hid))hid->claimed |= HID_CLAIMED_HIDDEV;if (!hidraw_connect(hid))hid->claimed |= HID_CLAIMED_HIDRAW;usb_set_intfdata(intf, hid);if (!hid->claimed) {printk ("HID device claimed by neither input, hiddev nor hidraw\n");hid_disconnect(intf);return -ENODEV;}if ((hid->claimed & HID_CLAIMED_INPUT))hid_ff_init(hid);if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),intf->cur_altsetting->desc.bInterfaceNumber);printk(KERN_INFO);if (hid->claimed & HID_CLAIMED_INPUT)printk("input");if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) ||hid->claimed & HID_CLAIMED_HIDRAW))printk(",");if (hid->claimed & HID_CLAIMED_HIDDEV)printk("hiddev%d", hid->minor);if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) &&(hid->claimed & HID_CLAIMED_HIDRAW))printk(",");if (hid->claimed & HID_CLAIMED_HIDRAW)printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor);c = "Device";for (i = 0; i < hid->maxcollection; i++) {if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&(hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&(hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {c = hid_types[hid->collection[i].usage & 0xffff];break;}}usb_make_path(interface_to_usbdev(intf), path, 63);printk(": USB HID v%x.%02x %s [%s] on %s\n",hid->version >> 8, hid->version & 0xff, c, hid->name, path);return0;}这个函数看起来是不是让⼈⼼慌慌?其实这个函数的最后⼀部份就是打印出⼀个Debug信息,我们根本就不需要去看. hiddev_connect()和hidraw_connect()是⼀个选择编译的操作,也不可以不要去理会.然后,剩下的就没多少了.3.1:usb_hid_configure()函数分析先来看usb_hid_configure().顾名思义,该接⼝⽤来配置hid设备.怎么配置呢?还是深⼊到代码来分析,该函数有⼀点长,分段分析如下:static struct hid_device *usb_hid_configure(struct usb_interface *intf){struct usb_host_interface *interface = intf->cur_altsetting;struct usb_device *dev = interface_to_usbdev (intf);struct hid_descriptor *hdesc;struct hid_device *hid;u32 quirks = 0;unsigned rsize = 0;char *rdesc;int n, len, insize = 0;struct usbhid_device *usbhid;quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));/* Many keyboards and mice don't like to be polled for reports,* so we will always set the HID_QUIRK_NOGET flag for them. *///如果是boot设备,跳出.不由此驱动处理if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)quirks |= HID_QUIRK_NOGET;}//如果是要忽略的if (quirks & HID_QUIRK_IGNORE)return NULL;if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&(interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))return NULL;⾸先找到该接⼝需要修正的操作,也就是上⾯代码中的quirks值,如果没有修正操作,则quirks为0.另外,根据usb hid spec中的定义,subclass如果为1,则说明该设备是⼀个boot阶段使⽤的hid设备,然后Protocol Code为1和2时分别代表Keyboard和Mouse. 如//get hid descriptorsif (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&(!interface->desc.bNumEndpoints ||usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {dbg_hid("class descriptor not present\n");return NULL;}//bNumDescriptors:⽀持的附属描述符数⽬for (n = 0; n < hdesc->bNumDescriptors; n++)if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);//如果Report_Descriptors长度不合法if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {dbg_hid("weird size of report descriptor (%u)\n", rsize);return NULL;}if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {dbg_hid("couldn't allocate rdesc memory\n");return NULL;}//Set idle_time = 0hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);//Get Report_Descriptorsif ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {dbg_hid("reading report descriptor failed\n");kfree(rdesc);return NULL;}//是否属于fixup?usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct), rdesc,rsize, rdesc_quirks_param);dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);for (n = 0; n < rsize; n++)dbg_hid_line(" %02x", (unsigned char) rdesc[n]);dbg_hid_line("\n");对于HID设备来说,在interface description之后会附加⼀个hid description, hid description中的最后部份包含有Report description或者Physical Descriptors的长度.在上⾯的代码中,⾸先取得附加在interface description之后的hid description,然后,再从hid description中取得report description的长度.最后,取得report description的详细信息.在这⾥,还会将idle时间设备为0,表⽰⽆限时,即,从上⼀次报表传输后,只有在报表发⽣改变时,才会传送此报表内容,否则,传送NAK.这段代码的最后⼀部份是相关的fixup操作,不做详细分析.//pasrse the report_descriptorif (!(hid = hid_parse_report(rdesc, n))) {dbg_hid("parsing report descriptor failed\n");kfree(rdesc);return NULL;}kfree(rdesc);hid->quirks = quirks;if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))goto fail_no_usbhid;hid->driver_data = usbhid;usbhid->hid = hid;解析获得的report description,解析之后的信息,存放在hid_device->collection和hid_device->report_enum[ ]中,这个解析过程之后会做详细分析.然后,初始化⼀个usbhid_device结构,使usbhid_device->hid指向刚解析report description获得的hid_device.同 usbhid->bufsize = HID_MIN_BUFFER_SIZE;//计算各传输⽅向的最⼤bufferhid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)usbhid->bufsize = HID_MAX_BUFFER_SIZE;//in⽅向的传输最⼤值hid_find_max_report(hid, HID_INPUT_REPORT, &insize);if (insize > HID_MAX_BUFFER_SIZE)insize = HID_MAX_BUFFER_SIZE;if (hid_alloc_buffers(dev, hid)) {hid_free_buffers(dev, hid);goto fail;}计算传输数据的最⼤缓存区,并以这个⼤⼩为了hid设备的urb传输分配空间.另外,这⾥有⼀个最⼩值限制即代码中所看到的HID_MIN_BUFFER_SIZE,为64, 即⼀个⾼速设备的⼀个端点⼀次传输的数据量.在这⾥定义最⼩值为64是为了照顾低速然后,调⽤hid_alloc_buffers()为hid的urb传输初始化传输缓冲区.另外,需要注意的是,insize为INPUT⽅向的最⼤数据传输量.// 初始化usbhid->urbin和usbhid->usboutfor (n = 0; n < interface->desc.bNumEndpoints; n++) {struct usb_endpoint_descriptor *endpoint;int pipe;int interval;endpoint = &interface->endpoint[n].desc;//不是中断传输退出if ((endpoint->bmAttributes & 3) != 3) /* Not an interrupt endpoint */continue;interval = endpoint->bInterval;/* Change the polling interval of mice. *///修正⿏标的双击时间if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)interval = hid_mousepoll_interval;if (usb_endpoint_dir_in(endpoint)) {if (usbhid->urbin)continue;if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))goto fail;pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize,hid_irq_in, hid, interval);usbhid->urbin->transfer_dma = usbhid->inbuf_dma;usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;} else {if (usbhid->urbout)continue;if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL)))goto fail;pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress);usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0,hid_irq_out, hid, interval);usbhid->urbout->transfer_dma = usbhid->outbuf_dma;usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;}}if (!usbhid->urbin) {err_hid("couldn't find an input interrupt endpoint");goto fail;}遍历接⼝中的所有endpoint,并初始化in中断传输⽅向和out中断⽅向的urb.如果⼀个hid设备没有in⽅向的中断传输,⾮法.另外,在这⾥要值得注意的是, 在为OUT⽅向urb初始化的时候,它的传输缓存区⼤⼩被设为了0.IN⽅向的中断传输缓存区⼤⼩被设为了insize,传输缓存区⼤⼩在submit的时候会修正的. init_waitqueue_head(&hid->wait);INIT_WORK(&usbhid->reset_work, hid_reset);setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);spin_lock_init(&usbhid->inlock);spin_lock_init(&usbhid->outlock);spin_lock_init(&usbhid->ctrllock);hid->version = le16_to_cpu(hdesc->bcdHID);hid->country = hdesc->bCountryCode;hid->dev = &intf->dev;usbhid->intf = intf;usbhid->ifnum = interface->desc.bInterfaceNumber;hid->name[0] = 0;if (dev->manufacturer)strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));if (dev->product) {if (dev->manufacturer)strlcat(hid->name, "", sizeof(hid->name));strlcat(hid->name, dev->product, sizeof(hid->name));}if (!strlen(hid->name))snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));hid->bus = BUS_USB;hid->vendor = le16_to_cpu(dev->descriptor.idVendor);hid->product = le16_to_cpu(dev->descriptor.idProduct);usb_make_path(dev, hid->phys, sizeof(hid->phys));strlcat(hid->phys, "/input", sizeof(hid->phys));len = strlen(hid->phys);if (len < sizeof(hid->phys) - 1)snprintf(hid->phys + len, sizeof(hid->phys) - len,"%d", intf->altsetting[0].desc.bInterfaceNumber);if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)hid->uniq[0] = 0;初始化hid的相关信息.//初始化hid 的ctrl传输usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);if (!usbhid->urbctrl)goto fail;usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,usbhid->ctrlbuf, 1, hid_ctrl, hid);usbhid->urbctrl->setup_dma = usbhid->cr_dma;usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);hid->hidinput_input_event = usb_hidinput_input_event;hid->hid_open = usbhid_open;hid->hid_close = usbhid_close;#ifdef CONFIG_USB_HIDDEVhid->hiddev_hid_event = hiddev_hid_event;hid->hiddev_report_event = hiddev_report_event;#endifhid->hid_output_raw_report = usbhid_output_raw_report;return hid;初始化usbhid的控制传输urb,之后⼜初始化了usbhid的⼏个操作函数.这个操作有什么⽤途,等⽤到的时候再来进⾏分析.fail:usb_free_urb(usbhid->urbin);usb_free_urb(usbhid->urbout);usb_free_urb(usbhid->urbctrl);hid_free_buffers(dev, hid);kfree(usbhid);fail_no_usbhid:hid_free_device(hid);return NULL;}经过上⾯的分析之后,我们对这个函数的⼤概操作有了⼀定的了解.现在分析⾥⾯调⽤的⼀些重要的⼦调函数.等这些⼦函数全部分析完了之后,不妨回过头看下这个函数.3.1.1:hid_parse_report()分析第⼀个要分析的函数是hid_parse_report().该函数⽤来解析report description.解析report description是⼀个繁杂的过程,对这个描述符不太清楚的,仔细看⼀下spec.在这⾥我们只会做代码上的分析.代码如下:struct hid_device *hid_parse_report(__u8 *start, unsigned size){struct hid_device *device;struct hid_parser *parser;struct hid_item item;__u8 *end;unsigned i;static int (*dispatch_type[])(struct hid_parser *parser,struct hid_item *item) = {hid_parser_main,hid_parser_global,hid_parser_local,hid_parser_reserved};if (!(device = kzalloc(sizeof(struct hid_device), GFP_KERNEL)))return NULL;//默认HID_DEFAULT_NUM_COLLECTIONS 项if (!(device->collection = kzalloc(sizeof(struct hid_collection) *HID_DEFAULT_NUM_COLLECTIONS, GFP_KERNEL))) {kfree(device);return NULL;}//hid_device->collection_size: collection的项数device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;for (i = 0; i < HID_REPORT_TYPES; i++)INIT_LIST_HEAD(&device->report_enum[i].report_list);if (!(device->rdesc = kmalloc(size, GFP_KERNEL))) {kfree(device->collection);kfree(device);return NULL;}//hid_device->rdesc存放report_descriptor,hid_device->size存放这个描述符的⼤⼩memcpy(device->rdesc, start, size);device->rsize = size;if (!(parser = vmalloc(sizeof(struct hid_parser)))) {kfree(device->rdesc);kfree(device->collection);kfree(device);return NULL;}memset(parser, 0, sizeof(struct hid_parser));parser->device = device;end = start + size;while ((start = fetch_item(start, end, &item)) != NULL) {//long item在这⾥暂不做parseif (item.format != HID_ITEM_FORMAT_SHORT) {dbg_hid("unexpected long global item\n");hid_free_device(device);vfree(parser);return NULL;}//parse the short itemif (dispatch_type[item.type](parser, &item)) {dbg_hid("item %u %u %u %u parsing failed\n",item.format, (unsigned)item.size, (unsigned)item.type, (unsigned)item.tag);hid_free_device(device);vfree(parser);return NULL;}//如果全部解析完了if (start == end) {if (parser->collection_stack_ptr) {dbg_hid("unbalanced collection at end of report description\n");hid_free_device(device);vfree(parser);return NULL;}if (parser->local.delimiter_depth) {dbg_hid("unbalanced delimiter at end of report description\n");hid_free_device(device);vfree(parser);return NULL;}vfree(parser);return device;}}dbg_hid("item fetching failed at offset %d\n", (int)(end - start));hid_free_device(device);vfree(parser);return NULL;}进⼊到这个函数,我们⾸先看到的是Main,Globa,Local标签的解析函数.然后,分配并初始化了hid_device结构和hid_ parser.在代码中我们看到,hid_ parser-> device指向了hid_device.后hid_device没有任何域指向hid_parser. 实际上hid_parser只是⼀个辅另外,hid_device-> rdesc保存了⼀份report description副本.然后,就开始对report description的解析.函数fetch_item()⽤来取出report description的⼀项数据.代码如下:static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item){u8 b;//合法性检测if ((end - start) <= 0)return NULL;//取前⾯⼀个字节.对于短项.它的⾸个字节定义了bsize,bType,bTag.⽽对于长项,它的值为0xFEb = *start++;item->type = (b >> 2) & 3;item->tag = (b >> 4) & 15;//如果为长项.它的Type和Tag在其后的⼆个字节中.item->data.longdata指向数据的起始位置if (item->tag == HID_ITEM_TAG_LONG) {item->format = HID_ITEM_FORMAT_LONG;if ((end - start) < 2)return NULL;item->size = *start++;item->tag = *start++;if ((end - start) < item->size)return NULL;item->data.longdata = start;start += item->size;return start;}//对于短项的情况.取得size值.并根据size值取得它的data域item->format = HID_ITEM_FORMAT_SHORT;item->size = b & 3;switch (item->size) {case0:return start;case1:if ((end - start) < 1)return NULL;item->data.u8 = *start++;return start;case2:if ((end - start) < 2)return NULL;item->data.u16 = le16_to_cpu(get_unaligned((__le16*)start));start = (__u8 *)((__le16 *)start + 1);return start;case3:item->size++;if ((end - start) < 4)return NULL;item->data.u32 = le32_to_cpu(get_unaligned((__le32*)start));start = (__u8 *)((__le32 *)start + 1);return start;}return NULL;}对照代码中的注释,应该很容易看懂这个函数,不再详细分析.返回到hid_parse_report()中,取得相应项之后,如果是长项,这⾥不会做处理.对于短项.为不同的type调⽤不同的解析函数.3.1.1.1:Global项解析Global的解析⼊⼝是hid_parser_global().代码如下:static int hid_parser_global(struct hid_parser *parser, struct hid_item *item){switch (item->tag) {//PUSH项case HID_GLOBAL_ITEM_TAG_PUSH:if (parser->global_stack_ptr == HID_GLOBAL_STACK_SIZE) {dbg_hid("global enviroment stack overflow\n");return -1;}memcpy(parser->global_stack + parser->global_stack_ptr++,&parser->global, sizeof(struct hid_global));return0;//POP项case HID_GLOBAL_ITEM_TAG_POP:if (!parser->global_stack_ptr) {dbg_hid("global enviroment stack underflow\n");return -1;}memcpy(&parser->global, parser->global_stack + --parser->global_stack_ptr,sizeof(struct hid_global));return0;case HID_GLOBAL_ITEM_TAG_USAGE_PAGE:parser->age_page = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM:parser->global.logical_minimum = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM:if (parser->global.logical_minimum < 0)parser->global.logical_maximum = item_sdata(item);elseparser->global.logical_maximum = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM:parser->global.physical_minimum = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM:if (parser->global.physical_minimum < 0)parser->global.physical_maximum = item_sdata(item);elseparser->global.physical_maximum = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:parser->global.unit_exponent = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_UNIT:parser->global.unit = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:if ((parser->global.report_size = item_udata(item)) > 32) {dbg_hid("invalid report_size %d\n", parser->global.report_size);return -1;}return0;case HID_GLOBAL_ITEM_TAG_REPORT_COUNT:if ((parser->global.report_count = item_udata(item)) > HID_MAX_USAGES) {dbg_hid("invalid report_count %d\n", parser->global.report_count);return -1;}return0;case HID_GLOBAL_ITEM_TAG_REPORT_ID:if ((parser->global.report_id = item_udata(item)) == 0) {dbg_hid("report_id 0 is invalid\n");return -1;}return0;default:dbg_hid("unknown global tag 0x%x\n", item->tag);return -1;}}这个函数虽然长,但是逻辑很简单,对于global信息,存放在hid_parse->global中.如果遇到了PUSH项,将当前的global项⼊栈,栈即为hid_parse-> global_stack[ ].当前的栈顶位置由hid_parse-> global_stack_ptr指定.如果遇到了POP项,就将栈中的global信息出栈.3.1.1.2:Local项解析Local项解析的相应接⼝为hid_parser_local().代码如下:static int hid_parser_local(struct hid_parser *parser, struct hid_item *item){__u32 data;unsigned n;if (item->size == 0) {dbg_hid("item data expected for local item\n");return -1;}data = item_udata(item);switch (item->tag) {//DELIMITER项,定义⼀个Local项的开始case HID_LOCAL_ITEM_TAG_DELIMITER://data>1:⼀个local项开始,0:⼀个local项结束//parse->local.delimiter_branch:表⽰local项计数.//进⼊⼀个local项时,local.delimiter_depth为1,退出⼀个local项时local.delimiter_depth为0// TODO: Local项不能嵌套if (data) {/** We treat items before the first delimiter* as global to all usage sets (branch 0).* In the moment we process only these global* items and the first delimiter set.*/if (parser->local.delimiter_depth != 0) {dbg_hid("nested delimiters\n");return -1;}parser->local.delimiter_depth++;parser->local.delimiter_branch++;} else {if (parser->local.delimiter_depth < 1) {dbg_hid("bogus close delimiter\n");return -1;}parser->local.delimiter_depth--;}return1;//以下各项不能出现在有DELIMITER标签的地⽅case HID_LOCAL_ITEM_TAG_USAGE:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}//local的usage项有扩展⽤法,它的⾼16可以定义usage_page.如果⾼16为空,它的//usage_page则定义在global中的usage_page if (item->size <= 2)data = (parser->age_page << 16) + data;//然后添加到parse->local的usage列表return hid_add_usage(parser, data);//对于有usage_min和usage_max的情况,将usage_min和usage_max之间的usage添加到//parse=>local的usage列表case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}if (item->size <= 2)data = (parser->age_page << 16) + data;parser->age_minimum = data;return0;case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}if (item->size <= 2)data = (parser->age_page << 16) + data;for (n = parser->age_minimum; n <= data; n++)if (hid_add_usage(parser, n)) {dbg_hid("hid_add_usage failed\n");return -1;}return0;default:dbg_hid("unknown local item tag 0x%x\n", item->tag);return0;}return0;}详细分析⼀下hid_add_usage().代码如下:static int hid_add_usage(struct hid_parser *parser, unsigned usage){if (parser->age_index >= HID_MAX_USAGES) {dbg_hid("usage index exceeded\n");return -1;}parser->age[parser->age_index] = usage;parser->local.collection_index[parser->age_index] =parser->collection_stack_ptr ?parser->collection_stack[parser->collection_stack_ptr - 1] : 0;parser->age_index++;return0;}如果usage项超过了HID_MAX_USAGES,为⾮法.最⼤为8192项.Parse->age_index表⽰local的项数,当然也表⽰了parse->age[ ]数组中的下⼀个可⽤项.parser->local.collection_index表⽰该usage所在的collection项序号.具体的collection信息存放在hid_deivce->collection[ ]中.关于collection我们在分析Main项解析的时候会详细分析.3.1.1.3:Main项解析Main项解析的⼊⼝为hid_parser_main().代码如下:static int hid_parser_main(struct hid_parser *parser, struct hid_item *item){__u32 data;int ret;//data域data = item_udata(item);switch (item->tag) {//Collectioncase HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:ret = open_collection(parser, data & 0xff);break;//End Collectioncase HID_MAIN_ITEM_TAG_END_COLLECTION:ret = close_collection(parser);break;//Inputcase HID_MAIN_ITEM_TAG_INPUT:ret = hid_add_field(parser, HID_INPUT_REPORT, data);break;//Outpputcase HID_MAIN_ITEM_TAG_OUTPUT:ret = hid_add_field(parser, HID_OUTPUT_REPORT, data);break;//Featurecase HID_MAIN_ITEM_TAG_FEATURE:ret = hid_add_field(parser, HID_FEATURE_REPORT, data);break;default:dbg_hid("unknown main item tag 0x%x\n", item->tag);ret = 0;}memset(&parser->local, 0, sizeof(parser->local)); /* Reset the local parser environment */return ret;}对Main项的解析要稍微复杂⼀点,Main项主要有两个部份,⼀个是Collection,⼀个是Input/Output/Feature项.先来看Collection项的解析.所有的collection信息都存放在hid_device->collection[ ]中.⽽Collection项⼜有嵌套的情况,每遇到⼀个Collection项就将collection的序号⼊栈,栈为parser_device->collection_stack[ ].栈顶指针为parser_device->collection_stack_ptr .遇到了⼀个end coll 熟悉这个⼤概的情况之后,就可以跟进open_collection()了.代码如下://所有的collection都存放在hid_dev->collection 中, ⽽hid_dev->maxcollection 表⽰collection[]中的下⼀个空闲位置//paser->collection_stack[ ]存放的是当前解析的collection在hid_dev->collection[ ]中的序号static int open_collection(struct hid_parser *parser, unsigned type){struct hid_collection *collection;unsigned usage;usage = parser->age[0];//colletcion嵌套过多if (parser->collection_stack_ptr == HID_COLLECTION_STACK_SIZE) {dbg_hid("collection stack overflow\n");return -1;}//device->maxcollection:存放的collection个数//device->collection[ ]太⼩,必须扩⼤存放空间if (parser->device->maxcollection == parser->device->collection_size) {collection = kmalloc(sizeof(struct hid_collection) *parser->device->collection_size * 2, GFP_KERNEL);if (collection == NULL) {dbg_hid("failed to reallocate collection array\n");return -1;}memcpy(collection, parser->device->collection,sizeof(struct hid_collection) *parser->device->collection_size);memset(collection + parser->device->collection_size, 0,sizeof(struct hid_collection) *parser->device->collection_size);kfree(parser->device->collection);parser->device->collection = collection;parser->device->collection_size *= 2;}//将collection序号⼊栈parser->collection_stack[parser->collection_stack_ptr++] =parser->device->maxcollection;//存⼊hid_device->collection[]collection = parser->device->collection +parser->device->maxcollection++;collection->type = type;collection->usage = usage;//collection的深度collection->level = parser->collection_stack_ptr - 1;if (type == HID_COLLECTION_APPLICATION)parser->device->maxapplication++;return0;}对照上⾯的分析和函数中的注释,理解这个函数应该很简单,不做详细分析.对于Input/Output/Feature项的解析:先来看⼀下hid_device结构的定义⽚段:struct hid_device{…………struct hid_report_enum report_enum[HID_REPORT_TYPES];……}对于INPUT/OUTPUT/FEATURE,每种类型都对应report_enum[ ]中的⼀项.Struct hid_report_enum定义如下:struct hid_report_enum {unsigned numbered;struct list_head report_list;struct hid_report *report_id_hash[256];};对于每⼀个report_id,对应report_id_hash[ ]中的⼀项,同时,将所对应的hid_report添加到report_list链表中.如果有多个report_id 的情况,numbered被赋为1.Struct hid_report定义如下:struct hid_report {struct list_head list;unsigned id; /* id of this report */unsigned type; /* report type */struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */unsigned maxfield; /* maximum valid field index */unsigned size; /* size of the report (bits) */struct hid_device *device; /* associated device */}List:⽤来形成链表Id:表⽰report_idType: INPUT/OUTPUT/FEATUREField[ ]:成员列表,对应⼀个report_id有多个INPUT(OUTPUT/FEATURE)项Maxfield: field[ ]中的有效项数Size: 该report的⼤⼩Device:所属的hid_device了解了这些之后,就可以来看⼀下代码了:如下:static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsigned flags){struct hid_report *report;struct hid_field *field;int usages;unsigned offset;int i;//找到类型和对应report_id所在的report.如果不存在,则新建之if (!(report = hid_register_report(parser->device, report_type, parser->global.report_id))) {dbg_hid("hid_register_report failed\n");return -1;}//对当前global数据的有效性判断。

Linux中如何把串口0改为普通串口使用

Linux中如何把串口0改为普通串口使用

Linux 中如何把串口 0 改为普通串口使用
开发板自带的 Linux 系统中,串口 0(对应设备/dev/ttySAC0)已经被用作控制台使用,如何把它 作为普通串口使用,需要改两个地方: 1. 在 bootloader 中把 Linux 启动参数的串口输出改为 null,根据使用 bootloader 的不同,可以又 分为两种情况:(1)使用 supervivi 作为 bootloader (2)使用 vboot 作为 bootloader 2. 屏蔽内核解压时的打印信息
ห้องสมุดไป่ตู้
下面是详细的修改步骤:
1. 在 bootloader 中修改启动内核启动参数 1.1 当使用 supervivi 作为 bootloader 时,修改 Linux 启动参数的方法
如果你使用了 supervivi 作为 bootloader,可以把内核启动时的输出信息屏蔽掉,或者改为其他 串口输出, 但无法去掉 supervivi 本身的输出信息。 参考用户手册 2.2.4 章节的方法修改, 如下: 在 BIOS 主菜单执行功能号[s],进入设置 Linux 启动参数子菜单,如图:
¦ò*AÞ=?±$i
Ö
_ ÈIFJ mach_type” ,再输入参数值
Linux_cmd_line 是经常用到的一个内核启动参数,例如要把内核的启动信息和登录终端改为串 口 1(默认是串口 0) ,则这样修改: 通过浏览参数,可以看到原来的参数:
Linux_cmd_line:noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0
(1)浏览当前参数设置[v] 输入“v”可以浏览当前启动参数设置情况:
E÷i “

Linux环境下基于MPC8250的SP接口驱动程序开发

Linux环境下基于MPC8250的SP接口驱动程序开发

◇ 具有独立的可编程波特率产生器;
◇ 时 钟 的相 位 和极性 可编程 :
◇ 开漏 输 出可支 持多 主工作 环境 ;
◇ 支 持本地 回环 测试 功能 。
2 l u 下 的设 备 驱 动 i x n
Ln x 备 驱 动 在 Ln x iu 设 iu 内核 中扮演 着 重要 的 角 色 。 它 们是 一 个 个 独立 的 “ 盒 子 ” 黑 。可 使某
1 电 子 元 器 件 主 用 20 .1 W . d c 2 0 71 e n c
维普资讯
第9 卷
2 7第1期 0 年 1 0 11 月
墓缔瘗
V1 N. o o1 . 1 9
NO .2 0 V 07
调 用映射 到 实 际硬件 设 备 的特有 操作 上 ,则是 驱
动 程序 的任 务 。这个 编程 接 口能 够使 驱 动 程序 独
vm

it ( m p n ma)
ar ea
_
(s ut fe , s u t tc i r l tc r
s u t ; 内存 重 映射操 作 tc r ) /
立 于 内核 的其它 部分 而建 立 ,必 要 的情 况 下 ,也
6x总 线 0
外 设 总 线
据线 (PD T ) S I A A 、主 发 从 收信 号 ( S) 主 收 MO I、
S I E S I OS S I IO S I K PS L PM I PM S P CL
从发 信 号 ( S )和 片选信 号 (PS L,可 用 在 MIO S IE )
特性 .给 出 了一个LN X系统环境 下 开发基 于MP 8 5 控制 器的S I 口驱动的程序 开发方 法。 IU C 20 P接 关键 词 :S I P ;驱动 ;l u ;MP 8 5 i x n C20

Linux设备驱动之USBhub驱动

Linux设备驱动之USBhub驱动

Linux设备驱动之USB hub驱动Linux设备驱动之USB hub驱动------------------------------------------本文系本站原创,欢迎!请注明出处:------------------------------------------一:前言继UHCI的驱动之后,我们对USB Control的运作有了一定的了解.在接下来的分析中,我们对USB设备的驱动做一个全面的分析,我们先从HUB的驱动说起.关于HUB,usb2.0 spec上有详细的定义,基于这部份的代码位于linux-2.6.25/drivers/usb/core下,也就是说,这部份代码是位于core下,和具体设备是无关的,因为各厂商的hub都是按照spec的要求来设计的.二:UHCI驱动中的root hub记得在分析UHCI驱动的时候,曾详细分析过root hub的初始化操作.为了分析方便,将代码片段列出如下:usb_add_hcd() à usb_alloc_dev():struct usb_device *usb_alloc_dev(struct usb_device *parent,struct usb_bus *bus, unsigned port1){…………//usb_device,内嵌有struct device结构,对这个结构进行初始化device_initialize(&dev->dev);dev->dev.bus = &usb_bus_type;dev->dev.type = &usb_device_type;…………}一看到前面对dev的赋值,根据我们对设备模型的理解,一旦这个device进行注册,就会发生driver和device的匹配过程了.不过,现在还不是分析这个过程的时候,我们先来看一下,USB子系统中的两种驱动.三:USB子系统中的两种驱动linux-2.6.25/drivers/usb/core/driver.c中,我们可以找到两种register driver的方式,分别为usb_register_driver()和usb_register_device_driver().分别来分析一下这两个接口.usb_register_device_driver()接口的代码如下:int usb_register_device_driver(struct usb_device_driver *new_udriver,struct module *owner){int retval = 0;if (usb_disabled())return -ENODEV;new_udriver->drvwrap.for_devices = 1;new_udriver-> = (char *) new_udriver->name;new_udriver->drvwrap.driver.bus = &usb_bus_type;new_udriver->drvwrap.driver.probe = usb_probe_device;new_udriver->drvwrap.driver.remove = usb_unbind_device;new_udriver->drvwrap.driver.owner = owner;retval = driver_register(&new_udriver->drvwrap.driver);if (!retval) {pr_info(“%s: registered new device driver %s\n”,usbcore_name, new_udriver->name);usbfs_update_special();} else {printk(KERN_ERR “%s: error %d registering device ““ driver %s\n”,usbcore_name, retval, new_udriver->name);}return retval;}首先,通过usb_disabled()来判断一下usb是否被禁用,如果被禁用,当然就不必执行下面的流程了,直接退出即可.从上面的代码,很明显可以看到, struct usb_device_driver 对struct device_driver进行了一次封装,我们注意一下这里的赋值操作:new_udriver->drvwrap.for_devices = 1.等等.这些在后面都是用派上用场的.usb_register_driver()的代码如下:int usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name){int retval = 0;if (usb_disabled())return -ENODEV;new_driver->drvwrap.for_devices = 0;new_driver-> = (char *) new_driver->name;new_driver->drvwrap.driver.bus = &usb_bus_type;new_driver->drvwrap.driver.probe = usb_probe_interface;new_driver->drvwrap.driver.remove = usb_unbind_interface;new_driver->drvwrap.driver.owner = owner;new_driver->drvwrap.driver.mod_name = mod_name;spin_lock_init(&new_driver->dynids.lock);INIT_LIST_HEAD(&new_driver->dynids.list);retval = driver_register(&new_driver->drvwrap.driver);if (!retval) {pr_info(“%s: registered new interface dr iver %s\n”,usbcore_name, new_driver->name);usbfs_update_special();usb_create_newid_file(new_driver);} else {printk(KERN_ERR “%s: error %d registering interface ““ driver %s\n”,usbcore_name, retval, new_driver->name);}return retval;}很明显,在这里接口里,将new_driver->drvwrap.for_devices设为了0.而且两个接口的porbe()函数也不一样.其实,对于usb_register_driver()可以看作是usb设备中的接口驱动,而usb_register_device_driver()是一个单纯的USB设备驱动.四: hub的驱动分析4.1: usb_bus_type->match()的匹配过程usb_bus_type->match()用来判断驱动和设备是否匹配,它的代码如下:static int usb_device_match(struct device *dev, struct device_driver *drv){/* 整理by *///usb device的情况if (is_usb_device(dev)) {/* interface drivers never match devices */ if (!is_usb_device_driver(drv))return 0;/* TODO: Add real matching code */ return 1;}//interface的情况else {struct usb_interface *intf;struct usb_driver *usb_drv;const struct usb_device_id *id;/*整理by */if (is_usb_device_driver(drv))return 0;intf = to_usb_interface(dev);usb_drv = to_usb_driver(drv);id = usb_match_id(intf, usb_drv->id_table);if (id)return 1;id = usb_match_dynamic_id(intf, usb_drv);if (id)return 1;}return 0;}这里的match会区分上面所说的两种驱动,即设备的驱动和接口的驱动. is_usb_device()的代码如下:static inline int is_usb_device(const struct device *dev){return dev->type == &usb_device_type;}很明显,对于root hub来说,这个判断是肯定会满足的.static inline int is_usb_device_driver(struct device_driver *drv){return container_of(drv, struct usbdrv_wrap, driver)->for_devices;}回忆一下,我们在分析usb_register_device_driver()的时候,不是将new_udriver->drvwrap.for_devices置为了1么?所以对于usb_register_device_driver()注册的驱动来说,这里也是会满足的.因此,对应root hub的情况,从第一个if就会匹配到usb_register_device_driver()注册的驱动.对于接口的驱动,我们等遇到的时候再来进行分析.4.2:root hub的驱动入口既然我们知道,root hub会匹配到usb_bus_type->match()的驱动,那这个驱动到底是什么呢?我们从usb子系统的初始化开始说起.在linux-2.6.25/drivers/usb/core/usb.c中.有这样的一段代码:subsys_initcall(usb_init);对于subsys_initcall()我们已经不陌生了,在很多地方都会遇到它.在系统初始化的时候,会调用到它对应的函数.在这里,即为usb_init().在usb_init()中,有这样的代码片段:static int __init usb_init(void){…………retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);if (!retval)goto out;……}在这里终于看到usb_register_device_driver()了. usb_generic_driver会匹配到所有usb 设备.定义如下:struct usb_device_driver usb_generic_driver = {.name = “usb”,.probe = generic_probe,.disconnect = generic_disconnect,#ifdefCONFIG_PM.suspend = generic_suspend,.resume = generic_resume,#endif.supports_autosuspend = 1,};现在是到分析probe()的时候了.我们这里说的并不是usb_generic_driver中的probe,而是封装在struct usb_device_driver中的driver对应的probe函数.在上面的分析, usb_register_device_driver()将封装的driver的probe()函数设置为了usb_probe_device().代码如下:static int usb_probe_device(struct device *dev){struct usb_device_driver *udriver = to_usb_device_driver(dev->driver);struct usb_device *udev;int error = -ENODEV;dev_dbg(dev, “%s\n”, __FUNCTION__);//再次判断dev是否是usb deviceif (!is_usb_device(dev)) /* Sanity check */return error;udev = to_usb_device(dev);/* TODO: Add real matching code *//* The device should always appear to be in use* unless the driver suports autosuspend.*///pm_usage_t: autosuspend计数.如果此计数为1,则不允许autosuspendudev->pm_usage_t = !(udriver->supports_autosuspend);error = udriver->probe(udev);return error;}首先,可以通过container_of()将封装的struct device, struct device_driver转换为struct usb_device和struct usb_device_driver.然后,再执行一次安全检查,判断dev是否是属于一个usb device.在这里,我们首次接触到了hub suspend.如果不支持suspend(udriver->supports_autosuspend为0),则udev->pm_usage_t被设为1,也就是说,它不允许设备suspend.否则,将其初始化为0. 最后,正如你所看到的,流程转入到了usb_device_driver->probe().对应到root hub,流程会转入到generic_probe().代码如下:static int generic_probe(struct usb_device *udev){int err, c;/* put device-specific files into sysfs */usb_create_sysfs_dev_files(udev);/* Choose and set the configuration.This registers the interfaces* with the driver core and lets interface drivers bind to them.*/if (udev->authorized == 0)dev_err(&udev->dev, “Device is not authorized for usage\n”);else {//选择和设定一个配置c = usb_choose_configuration(udev);if (c >= 0) {err = usb_set_configuration(udev, c);if (err) {dev_err(&udev->dev, “can’t set config #%d, error %d\n”,c, err);/* This need not be fatal.The user can try to* set other configurations. */}}}/* USB device state == configured ... usable */usb_notify_add_device(udev);return 0;}usb_create_sysfs_dev_files()是在sysfs中显示几个属性文件,不进行详细分析,有兴趣的可以结合之前分析的>来看下代码.usb_notify_add_device()是有关notify链表的操作,这里也不做详细分析.至于udev->authorized,在root hub的初始化中,是会将其初始化为1的.后面的逻辑就更简单了.为root hub 选择一个配置然后再设定这个配置.还记得我们在分析root hub的时候,在usb_new_device()中,会将设备的所有配置都取出来,然后将它们放到了usb_device-> config.现在这些信息终于会派上用场了.不太熟悉的,可以看下本站之前有关usb控制器驱动的文档.Usb2.0 spec上规定,对于hub设备,只能有一个config,一个interface,一个endpoint.实际上,在这里,对hub的选择约束不大,反正就一个配置,不管怎么样,选择和设定都是这个配置.不过,为了方便以后的分析,我们还是跟进去看下usb_choose_configuration()和usb_set_configuration()的实现.实际上,经过这两个函数之后,设备的probe()过程也就会结束了.4.2.1:usb_choose_configuration()函数分析usb_choose_configuration()的代码如下://为usb device选择一个合适的配置int usb_choose_configuration(struct usb_device *udev){int i;int num_configs;int insufficient_power = 0;struct usb_host_config *c, *best;best = NULL;//config数组c = udev->config;//config项数num_configs = udev->descriptor.bNumConfigurations;//遍历所有配置项for (i = 0; istruct usb_interface_descriptor *desc = NULL;/* It’s possible that a config has no interfaces! *///配置项的接口数目//取配置项的第一个接口if (c->desc.bNumInterfaces > 0)desc = &c->intf_cache[0]->altsetting->desc;/** HP’s USB bus-powered keyboard has only one configuration * and it claims to be self-powered; other devices may have* similar errors in their descriptors.If the next test* were allowed to execute, such configurations would always* be rejected and the devices would not work as expected.* In the meantime, we run the risk of selecting a config* that requires external power at a time when that power* isn’t available.It seems to be the lesser of two evils.** Bugzilla #6448 reports a device that appears to crash* when it receives a GET_DEVICE_STATUS request!We don’t * have any other way to tell whether a device is self-powered,* but since we don’t use that information anywhere but here,* the call has been removed.** Maybe the GET_DEVICE_STATUS call and the test below can* be reinstated when device firmwares bee more reliable.* Don’t hold your breath.*/#if 0/* Rule out self-powered configs for a bus-powered device */ if (bus_powered && (c->desc.bmAttributes &USB_CONFIG_ATT_SELFPOWER))continue;#endif/** The next test may not be as effective as it should be.* Some hubs have errors in their descriptor, claiming* to be self-powered when they are really bus-powered.* We will overestimate the amount of current such hubs* make available for each port.** This is a fairly benign sort of failure.It won’t* cause us to reject configurations that we should have* accepted.*//* Rule out configs that draw too much bus current *///电源不足.配置描述符中的电力是所需电力的1/2if (c->desc.bMaxPower * 2 > udev->bus_mA) {insufficient_power++;continue;}/* When the first config’s first interface is one of Microsoft’s* pet nonstandard Ethernet-over-USB protocols, ignore it unless* this kernel has enabled the necessary host side driver.*/if (i == 0 && desc && (is_rndis(desc) || is_activesync(desc))) {#if !defined(CONFIG_USB_NET_RNDIS_HOST) && !defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)continue;#elsebest = c;#endif}/* From the remaining configs, choose the first one whose* first interface is for a non-vendor-specific class.* Reason: Linux is more likely to have a class driver* than a vendor-specific driver. *///选择一个不是USB_CLASS_VENDOR_SPEC的配置else if (udev->descriptor.bDeviceClass !=USB_CLASS_VENDOR_SPEC &&(!desc || desc->bInterfaceClass !=USB_CLASS_VENDOR_SPEC)) {best = c;break;}/* If all the remaining configs are vendor-specific,* choose the first one. */else if (!best)best = c;}if (insufficient_power > 0)dev_info(&udev->dev, “rejected %d configuration%s ““due to insufficient available bus power\n”,insufficient_power, plural(insufficient_power));//如果选择好了配置,返回配置的序号,否则,返回-1if (best) {i = best->desc.bConfigurationValue;dev_info(&udev->dev,“configuration #%d chosen from %d choice%s\n”,i, num_configs, plural(num_configs));} else {i = -1;dev_warn(&udev->dev,“no configuration chosen from %d choice%s\n”,num_configs, plural(num_configs));}return i;}Linux按照自己的喜好选择好了配置之后,返回配置的序号.不过对于HUB来说,它有且仅有一个配置.4.2.2:usb_set_configuration()函数分析既然已经选好配置了,那就告诉设备选好的配置,这个过程是在usb_set_configuration()中完成的.它的代码如下:int usb_set_configuration(struct usb_device *dev, int configuration){int i, ret;struct usb_host_config *cp = NULL;struct usb_interface **new_interfaces = NULL;int n, nintf;if (dev->authorized == 0 || configuration == -1) configuration = 0;else {for (i = 0; i descriptor.bNumConfigurations; i++) {if (dev->config.desc.bConfigurationValue ==configuration) {cp = &dev->config;break;}}}if ((!cp && configuration != 0))return -EINV AL;/* The USB spec says configuration 0 means unconfigured. * But if a device includes a configuration numbered 0,* we will accept it as a correctly configured state.* Use -1 if you really want to unconfigure the device.*/if (cp && configuration == 0)dev_warn(&dev->dev, “config 0 descriptor??\n”);首先,根据选择好的配置号找到相应的配置,在这里要注意了, dev->config[]数组中的配置并不是按照配置的序号来存放的,而是按照遍历到顺序来排序的.因为有些设备在发送配置描述符的时候,并不是按照配置序号来发送的,例如,配置2可能在第一次GET_CONFIGURATION 就被发送了,而配置1可能是在第二次GET_CONFIGURATION才能发送.取得配置描述信息之后,要对它进行有效性判断,注意一下本段代码的最后几行代码:usb2.0 spec上规定,0号配置是无效配置,但是可能有些厂商的设备并末按照这一约定,所以在linux 中,遇到这种情况只是打印出警告信息,然后尝试使用这一配置./* Allocate memory for new interfaces before doing anything else,* so that if we run out then nothing will have changed. */n = nintf = 0;if (cp) {//接口总数nintf = cp->desc.bNumInterfaces;//interface指针数组,new_interfaces = kmalloc(nintf * sizeof(*new_interfaces),GFP_KERNEL);if (!new_interfaces) {dev_err(&dev->dev, “Out of memory\n”);return -ENOMEM;}for (; nnew_interfaces[n] = kzalloc(sizeof(struct usb_interface),GFP_KERNEL);if (!new_interfaces[n]) {dev_err(&dev->dev, “Out of memory\n”);ret = -ENOMEM;free_interfaces:while (--n >= 0)kfree(new_interfaces[n]);kfree(new_interfaces);return ret;}}//如果总电源小于所需电流,打印警告信息i = dev->bus_mA - cp->desc.bMaxPower * 2;if (idev_warn(&dev->dev, “new config #%d exceeds power ““limit by %dmA\n”,configuration, -i);}在这里,注要是为new_interfaces分配空间,要这意的是, new_interfaces是一个二级指针,它的最终指向是struct usb_interface结构.特别的,如果总电流数要小于配置所需电流,则打印出警告消息.实际上,这种情况在usb_choose_configuration()中已经进行了过滤./* Wake up the device so we can send it the Set-Config request */ //要对设备进行配置了,先唤醒它ret = usb_autoresume_device(dev);if (ret)goto free_interfaces;/* if it’s already configured, clear out old state first.* getting rid of old interfaces means unbinding their drivers.*///不是处于ADDRESS状态,先清除设备的状态if (dev->state != USB_STATE_ADDRESS)usb_disable_device(dev, 1); /* Skip ep0 *///发送控制消息,选取配置ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),USB_REQ_SET_CONFIGURATION, 0, configuration, 0,NULL, 0, USB_CTRL_SET_TIMEOUT);if (ret/* All the old state is gone, so what else can we do?* The device is probably useless now anyway.*/cp = NULL;}//dev->actconfig存放的是当前设备选取的配置dev->actconfig = cp;if (!cp) {usb_set_device_state(dev, USB_STA TE_ADDRESS);usb_autosuspend_device(dev);goto free_interfaces;}//将状态设为CONFIGUREDusb_set_device_state(dev, USB_STA TE_CONFIGURED);接下来,就要对设备进行配置了,首先,将设备唤醒.回忆一下我们在分析UHCI驱动时,列出来的设备状态图.只有在ADDRESS状态才能转入到CONFIG状态.(SUSPEND状态除外). 所以,如果设备当前不是处于ADDRESS状态,就需要将设备的状态初始化.usb_disable_device()函数是个比较重要的操作,在接下来再对它进行详细分析.接着,发送SET_CONFIGURA TION的Control消息给设备,用来选择配置最后,将dev->actconfig指向选定的配置,将设备状态设为CONFIG/* Initialize the new interface structures and the* hc/hcd/usbcore interface/endpoint state.*///遍历所有的接口for (i = 0; istruct usb_interface_cache *intfc;struct usb_interface *intf;struct usb_host_interface *alt;cp->interface = intf = new_interfaces;intfc = cp->intf_cache;intf->altsetting = intfc->altsetting;intf->num_altsetting = intfc->num_altsetting;//是否关联的接口描述符,定义在minor usb 2.0 spec中intf->intf_assoc = find_iad(dev, cp, i);kref_get(&intfc->ref);//选择0号设置alt = usb_altnum_to_altsetting(intf, 0);/* No altsetting 0?We’ll assume the first altsetting.* We could use a GetInterface call, but if a device is* so non-pliant that it doesn’t have altsetting 0* then I would n’t trust its reply anyway.*///如果0号设置不存在,选排在第一个设置if (!alt)alt = &intf->altsetting[0];//当前的配置intf->cur_altsetting = alt;usb_enable_interface(dev, intf);intf->dev.parent = &dev->dev;intf->dev.driver = NULL;intf->dev.bus = &usb_bus_type;intf->dev.type = &usb_if_device_type;intf->dev.dma_mask = dev->dev.dma_mask;device_initialize(&intf->dev);mark_quiesced(intf);sprintf(&intf->dev.bus_id[0], “%d-%s:%d.%d”,dev->bus->busnum, dev->devpath,configuration, alt->desc.bInterfaceNumber);}kfree(new_interfaces);if (cp->string == NULL)cp->string = usb_cache_string(dev, cp->desc.iConfiguration);之前初始化的new_interfaces在这里终于要派上用场了.初始化各接口,从上面的初始化过程中,我们可以看出:Intf->altsetting,表示接口的各种设置Intf->num_altsetting:表示接口的设置数目Intf->intf_assoc:接口的关联接口(定义于minor usb 2.0 spec)Intf->cur_altsetting:接口的当前设置.结合之前在UHCI中的分析,我们总结一下:Usb_dev->config,其实是一个数组,存放设备的配置.usb_dev->config[m]-> interface[n]表示第m个配置的第n个接口的intercace结构.(m,bsp; dev->bus->busnum, dev->devpath,configuration, alt->desc.bInterfaceNumber);dev指的是这个接口所属的usb_dev,结合我们之前在UHCI中关于usb设备命名方式的描述.可得出它的命令方式如下:USB总线号-设备路径:配置号.接口号.例如,在我的虚拟机上:[rootlocalhost devices]# pwd/sys/bus/usb/devices[rootlocalhost devices]# ls1-0:1.0usb1[rootlocalhost devices]#可以得知,系统只有一个usb control.1-0:1.0:表示,第一个usb cont意思上看来,它是标记接口为停止状态.它的”反函数”是mark_active().两个函数如下示:static inline void mark_active(struct usb_interface *f){f->is_active = 1;f->dev.power.power_state.event = PM_EVENT_ON;}static inline void mark_quiesced(struct usb_interface *f){f->is_active = 0;f->dev.power.power_state.event = PM_EVENT_SUSPEND; }从代码看来,它只是对接口的活动标志(is_active)进行了设置./* Now that all the interfaces are set up, register them* to trigger binding of drivers to interfaces.probe()* routines may install different altsettings and may* claim() any interfaces not yet bound.Many class drivers* need that: CDC, audio, video, etc.*///注册每一个接口?for (i = 0; istruct usb_interface *intf = cp->interface;dev_dbg(&dev->dev,“addi ng %s (config #%d, interface %d)\n”,intf->dev.bus_id, configuration,intf->cur_altsetting->desc.bInterfaceNumber);ret = device_add(&intf->dev);if (ret != 0) {dev_err(&dev->dev, “device_add(%s) --> %d\n”,intf->dev.bus_id, ret);continue;}usb_create_sysfs_intf_files(intf);}//使设备suspendusb_autosuspend_device(dev);return 0;}最后,注册intf内嵌的device结构.设备配置完成了,为了省电,可以将设备置为SUSPEND状态.这个函数中还有几个比较重要的子函数,依次分析如下:1: usb_disable_device()函数.顾名思义,这个函数是将设备disable掉.代码如下:void usb_disable_device(struct usb_device *dev, int skip_ep0){int i;dev_dbg(&dev->dev, “%s nuking %s URBs\n”, __FUNCTION__, skip_ep0 ? “non-ep0” : “all”);for (i = skip_ep0; iusb_disable_endpoint(dev, i);usb_disable_endpoint(dev, i + USB_DIR_IN);}dev->toggle[0] = dev->toggle[1] = 0;/* getting rid of interfaces will disconnect* any drivers bound to them (a key side effect)*/if (dev->actconfig) {for (i = 0; i actconfig->desc.bNumInterfaces; i++) {struct usb_interface *interface;/* remove this interface if it has been registered */interface = dev->actconfig->interface;if (!device_is_registered(&interface->dev))continue;dev_dbg(&dev->dev, “unregistering interface %s\n”,interface->dev.bus_id);usb_remove_sysfs_intf_files(interface);device_del(&interface->dev);}/* Now that the interfaces are unbound, nobody should* try to access them.*/for (i = 0; i actconfig->desc.bNumInterfaces; i++) {put_device(&dev->actconfig->interface->dev);dev->actconfig->interface = NULL;}dev->actconfig = NULL;if (dev->state == USB_STATE_CONFIGURED)usb_set_device_state(dev, USB_STATE_ADDRESS);}}第二个参数是skip_ep0.是表示是否跳过ep0.为1表示跳过,为0表示清除掉设备中的所有endpoint.这个函数可以分为两个部份,一部份是对usb_dev中的endpoint进行操作,一方面是释放usb_dev的选定配置项.对于第一部份:从代码中可能看到,如果skip_ep0为1,那就是从1开始循环,所以,就跳过了ep0.另外,一个端点号对应了两个端点,一个IN,一个OUT.IN端点比OUT端点要大USB_DIR_IN.另外,既然设备都已经被禁用了,那toggle也应该回归原位了.因些将两个方向的toggle都设为0. usb_disable_endpoint()是一个很有意思的处理.它的代码如下:void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr){unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK;struct usb_host_endpoint *ep;if (!dev)return;//在dev->ep_out和dev->ep_in删除endpointif (usb_endpoint_out(epaddr)) {ep = dev->ep_out[epnum];dev->ep_out[epnum] = NULL;} else {ep = dev->ep_in[epnum];dev->ep_in[epnum] = NULL;}//禁用掉此ep.包括删除ep上提交的urb 和ep上的QHif (ep) {ep->enabled = 0;usb_hcd_flush_endpoint(dev, ep);usb_hcd_disable_endpoint(dev, ep);}}在dev->ep_in[]/dev->ep_out[]中删除endpoint,这点很好理解.比较难以理解的是后面的两个操作,即usb_hcd_flush_endpoint()和usb_hcd_disable_endpoint().根据之前分析的UHCI的驱动,我们得知,对于每个endpoint都有一个传输的qh,这个qh上又挂上了要传输的urb.因此,这两个函数一个是删除urb,一个是删除qh.usb_hcd_flush_endpoint()的代码如下:void usb_hcd_flush_endpoint(struct usb_device *udev,struct usb_host_endpoint *ep){struct usb_hcd *hcd;struct urb *urb;if (!ep)return;might_sleep();hcd = bus_to_hcd(udev->bus);/* No more submits can occur *///在提交urb时,将urb加到ep->urb_list上的时候要持锁//因此,这里持锁的话,无法发生中断和提交urbspin_lock_irq(&hcd_urb_list_lock);rescan://将挂在ep->urb_list上的所有urb unlink.注意这里unlink一般只会设置urb->unlinked的//值,不会将urb从ep->urb_list上删除.只有在UHCI的中断处理的时候,才会调用//uhci_giveback_urb()将其从ep->urb_list中删除list_for_each_entry (urb, &ep->urb_list, urb_list) {int is_in;if (urb->unlinked)continue;usb_get_urb (urb);is_in = usb_urb_dir_in(urb);spin_unlock(&hcd_urb_list_lock);/* kick hcd */unlink1(hcd, urb, -ESHUTDOWN);dev_dbg (hcd->self.controller,“shutdown urb %p ep%d%s%s\n”,urb, usb_endpoint_num(&ep->desc),is_in ? “in” : “out”,({char *s;switch (usb_endpoint_type(&ep->desc)) {case USB_ENDPOINT_XFER_CONTROL:s = ““; break;case USB_ENDPOINT_XFER_BULK:s = “-bulk”; break;case USB_ENDPOINT_XFER_INT:s = “-intr”; break;default:s = “-iso”; break;};s;}));usb_put_urb (urb);/* list contents may have changed *///在这里解开锁了,对应ep->urb_list上又可以提交urb. //这里释放释的话,主要是为了能够产生中断spin_lock(&hcd_urb_list_lock);goto rescan;}spin_unlock_irq(&hcd_urb_list_lock);/* Wait until the endpoint queue is pletely empty *///等待urb被调度完while (!list_empty (&ep->urb_list)) {spin_lock_irq(&hcd_urb_list_lock);/* The list may have changed while we acquired the spinlock */urb = NULL;if (!list_empty (&ep->urb_list)) {urb = list_entry (ep->urb_list.prev, struct urb,urb_list);usb_get_urb (urb);}spin_unlock_irq(&hcd_urb_list_lock);if (urb) {usb_kill_urb (urb);usb_put_urb (urb);}}}仔细体会这里的代码,为什么在前一个循环中,要使用goto rescan重新开始这个循环呢?这是因为在后面已经将自旋锁释放了,因此,就会有这样的可能,在函数中操作的urb,可能已经被调度完释放了.因此,再对这个urb操作就会产生错误.所以,需要重新开始这个循环.那后一个循环又是干什么的呢?后一个循环就是等待urb被调度完.有人就会有这样的疑问了,这里一边等待,然后endpoint一边还提交urb,那这个函数岂不是要耗掉很长时间?在这里,不要忘记了前面的操作,在调这个函数之前, usb_disable_endpoint()已经将这个endpoint禁用了,也就是说该endpoint不会产生新的urb.因为,在后一个循环中,只需要等待那些被unlink的urb调度完即可.在usb_kill_urb()中,会一直等待,直到这个urb被调度完成为止.可能有人又会有这样的疑问:Usb_kill_urb()中也有unlink urb的操作,为什么这里要分做两个循环呢?另外的一个函数是usb_hcd_disable_endpoint().代码如下:void usb_hcd_disable_endpoint(struct usb_device *udev,struct usb_host_endpoint *ep){struct usb_hcd *hcd;might_sleep();hcd = bus_to_hcd(udev->bus);if (hcd->driver->endpoint_disable)hcd->driver->endpoint_disable(hcd, ep);}从上面的代码可以看到,操作转向了hcd->driver的endpoint_disable()接口.以UHCI为例.在UHCI中,对应的接口为:static void uhci_hcd_endpoint_disable(struct usb_hcd *hcd,struct usb_host_endpoint *hep){struct uhci_hcd *uhci = hcd_to_uhci(hcd); struct uhci_qh *qh;spin_lock_irq(&uhci->lock);qh = (struct uhci_qh *) hep->hcpriv;if (qh == NULL)goto done;while (qh->state != QH_STA TE_IDLE) { ++uhci->num_waiting;spin_unlock_irq(&uhci->lock);wait_event_interruptible(uhci->waitqh, qh->state == QH_STATE_IDLE); spin_lock_irq(&uhci->lock);--uhci->num_waiting;}uhci_free_qh(uhci, qh);done:spin_unlock_irq(&uhci->lock);}这个函数没啥好说的,就是在uhci->waitqh上等待队列状态变为QH_STATE_IDLE.来回忆一下,qh在什么情况下才会变为QH_STATE_IDLE呢? 是在qh没有待传输的urb的时候.然后,将qh释放.现在我们来接着看usb_disable_device()的第二个部份.第二部份主要是针对dev->actconfig进行的操作, dev->actconfig存放的是设备当前的配置,现在要将设备设回Address状态.就些东西当然是用不了上的了.释放dev->actconfig->interface[]中的各元素,注意不要将dev->actconfig->interface[]所指向的信息释放了,它都是指向dev->config[]-> intf_cache[]中的东西,这些东西一释放,usb device在Get_ Configure所获得的信息就会部丢失了.就这样, usb_disable_device()函数也走到了尾声.2: usb_cache_string()函数这个函数我们在分析UHCI的时候已经接触过,但末做详细的分析.首先了解一下这个函数的作用,有时候,为了形象的说明,会提供一个字符串形式的说明.例如,对于配置描述符来说,它的iConfiguration就表示一个字符串索引,然后用Get_String就可以取得这个索引所对应的字串了.不过,事情并不是这么简单.因为字符串对应不同的编码,所以这里还会对应有编码的处理.来看具体的代码:char *usb_cache_string(struct usb_device *udev, int index){char *buf;char *smallbuf = NULL;int len;if (indexreturn NULL;//不知道字符到底有多长,就按最长256字节处理buf = kmalloc(256, GFP_KERNEL);if (buf) {len = usb_string(udev, index, buf, 256);//取到字符了,分配合适的长度if (len > 0) {smallbuf = kmalloc(++len, GFP_KERNEL);if (!smallbuf)return buf;//将字符copy过去memcpy(smallbuf, buf, len);}//释放旧空间kfree(buf);}return smallbuf;}这个函数没啥好说的,流程转入到usb_string中.代码如下:int usb_string(struct usb_device *dev, int index, char *buf, size_t size) {unsigned char *tbuf;int err;unsigned int u, idx;if (dev->state == USB_STATE_SUSPENDED)return -EHOSTUNREACH;if (sizereturn -EINV AL;buf[0] = 0;tbuf = kmalloc(256, GFP_KERNEL);if (!tbuf)return -ENOMEM;/* get langid for strings if it’s not yet known *///先取得设备支持的编码IDif (!dev->have_langid) {//以0号序号和编码0,Get_String就可得到设备所支持的编码列表err = usb_string_sub(dev, 0, 0, tbuf);//如果发生了错误,或者是取得的数据超短(最短为4字节)if (errdev_err(&dev->dev,“string descriptor 0 read error: %d\n”,err);goto errout;} else if (errdev_err(&dev->dev, “string desc riptor 0 too short\n”);err = -EINV AL;goto errout;}//取设备支持的第一个编码else {dev->have_langid = 1;dev->string_langid = tbuf[2] | (tbuf[3]/* always use the first langid listed */dev_dbg(&dev->dev, “default language 0x%04x\n”,dev->string_langid);}}//以编码ID和序号Index作为参数Get_String取得序号对应的字串err = usb_string_sub(dev, dev->string_langid, index, tbuf);if (errgoto errout;//空一个字符来用来存放结束符size--; /* leave room for trailing NULL char in output buffer */ //两字节一组,(Unicode编码的)for (idx = 0, u = 2; uif (idx >= size)break;//如果高字节有值,说明它不是ISO-8859-1编码的,将它置为? //否则,就将低位的值存放到buf中if (tbuf[u+1]) /* high byte */buf[idx++] = ‘?’;/* non ISO-8859-1 character */elsebuf[idx++] = tbuf;}//在最后一位赋0,字串结尾buf[idx] = 0;//返回字串的长度,(算上了最后的结尾字符)err = idx;//如果该描述符不是STRING描述符,打印出错误提示if (tbuf[1] != USB_DT_STRING)dev_dbg(&dev->dev,“wrong descriptor type %02x for string %d (\”%s\”)\n”,tbuf[1], index, buf);。

linux设备驱动spi详解5-应用到驱动的完整流程

linux设备驱动spi详解5-应用到驱动的完整流程

linux设备驱动spi详解5-应⽤到驱动的完整流程所有的应⽤程序使⽤dev/⽬录下创建的设备,这些字符设备的操作函数集在⽂件spidev.c中实现。

1static const struct file_operations spidev_fops = {2 .owner = THIS_MODULE,3/* REVISIT switch to aio primitives, so that userspace4 * gets more complete API coverage. It'll simplify things5 * too, except for the locking.6*/7 .write = spidev_write,8 .read = spidev_read,9 .unlocked_ioctl = spidev_ioctl,10 .open = spidev_open,11 .release = spidev_release,12 };1 spidev_ioctl函数以上是所有应⽤程序所能够做的所有操作,由此开始追踪spi 驱动程序的完整执⾏流程其中,最重要的就是ioctl, 从这⾥开始先重点剖析ioctl函数1 spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)2 {3int err = 0;4int retval = 0;5struct spidev_data *spidev;6struct spi_device *spi;7 u32 tmp;8 unsigned n_ioc;9struct spi_ioc_transfer *ioc;10//ioctl cmd 检查11/* Check type and command number */12if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)13return -ENOTTY;1415/* Check access direction once here; don't repeat below.16 * IOC_DIR is from the user perspective, while access_ok is17 * from the kernel perspective; so they look reversed.18*/19if (_IOC_DIR(cmd) & _IOC_READ)20 err = !access_ok(VERIFY_WRITE,21 (void __user *)arg, _IOC_SIZE(cmd));22if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)23 err = !access_ok(VERIFY_READ,24 (void __user *)arg, _IOC_SIZE(cmd));25if (err)26return -EFAULT;2728/* guard against device removal before, or while,29 * we issue this ioctl.30*/31//通过以下⽅式获取spi_device-----spi(是之后操作的基础)32 spidev = filp->private_data;33 spin_lock_irq(&spidev->spi_lock);34 spi = spi_dev_get(spidev->spi);35 spin_unlock_irq(&spidev->spi_lock);3637if (spi == NULL)38return -ESHUTDOWN;3940/* use the buffer lock here for triple duty:41 * - prevent I/O (from us) so calling spi_setup() is safe;42 * - prevent concurrent SPI_IOC_WR_* from morphing43 * data fields while SPI_IOC_RD_* reads them;44 * - SPI_IOC_MESSAGE needs the buffer locked "normally".45*/46 mutex_lock(&spidev->buf_lock);47//以上是进⾏check,检查命令有效性,以及进⾏初始化数据,这⾥不在多做说明48switch (cmd) {49/* read requests */50case SPI_IOC_RD_MODE://获取模式信息,将信息发送给⽤户51 retval = __put_user(spi->mode & SPI_MODE_MASK,52 (__u8 __user *)arg);53break;54case SPI_IOC_RD_LSB_FIRST://获取spi最低有效位55 retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,56 (__u8 __user *)arg);57break;58case SPI_IOC_RD_BITS_PER_WORD:59 retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);61case SPI_IOC_RD_MAX_SPEED_HZ:62 retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);63break;6465/* write requests */66case SPI_IOC_WR_MODE://设置数据传输模式,这⾥只是把设置的数据保存在spi 中,但并没有对spi device做任何操作,对spi device的操作⼀并在最后进⾏ 67 retval = __get_user(tmp, (u8 __user *)arg);68if (retval == 0) {69 u8 save = spi->mode;7071if (tmp & ~SPI_MODE_MASK) {72 retval = -EINVAL;73break;74 }7576 tmp |= spi->mode & ~SPI_MODE_MASK;77 spi->mode = (u8)tmp;78 retval = spi_setup(spi);79if (retval < 0)80 spi->mode = save;81else82 dev_dbg(&spi->dev, "spi mode %02xn", tmp);83 }84break;85case SPI_IOC_WR_LSB_FIRST://设置设置spi写最低有效位,同上86 retval = __get_user(tmp, (__u8 __user *)arg);87if (retval == 0) {88 u8 save = spi->mode;8990if (tmp)91 spi->mode |= SPI_LSB_FIRST;92else93 spi->mode &= ~SPI_LSB_FIRST;94 retval = spi_setup(spi);95if (retval < 0)96 spi->mode = save;97else98 dev_dbg(&spi->dev, "%csb firstn",99 tmp ? 'l' : 'm');100 }101break;102case SPI_IOC_WR_BITS_PER_WORD://设置spi写每个字含多个个位,同上103 retval = __get_user(tmp, (__u8 __user *)arg);104if (retval == 0) {105 u8 save = spi->bits_per_word;106107 spi->bits_per_word = tmp;108 retval = spi_setup(spi);109if (retval < 0)110 spi->bits_per_word = save;111else112 dev_dbg(&spi->dev, "%d bits per wordn", tmp);113 }114break;115case SPI_IOC_WR_MAX_SPEED_HZ://设置spi写最⼤速率,同上116 retval = __get_user(tmp, (__u32 __user *)arg);117if (retval == 0) {118 u32 save = spi->max_speed_hz;119120 spi->max_speed_hz = tmp;121 retval = spi_setup(spi);122if (retval < 0)123 spi->max_speed_hz = save;124else125 dev_dbg(&spi->dev, "%d Hz (max)n", tmp);126 }127break;128129default:130/* segmented and/or full-duplex I/O request */131if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))//查看是否为数据write命令132 || _IOC_DIR(cmd) != _IOC_WRITE) {133 retval = -ENOTTY;134break;135 }136//pay more time on understanding below method137 tmp = _IOC_SIZE(cmd);//从命令参数中解析出⽤户数据⼤⼩138if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {//数据⼤⼩必须是struct spi_ioc_transfer的整数倍139 retval = -EINVAL;140break;141 }142 n_ioc = tmp / sizeof(struct spi_ioc_transfer);//将要传输的数据分成n个传输数据段143if (n_ioc == 0)145146/* copy into scratch area */147 ioc = kmalloc(tmp, GFP_KERNEL);//获取n个数据段的数据管理结构体的内存空间148if (!ioc) {149 retval = -ENOMEM;150break;151 }152if (__copy_from_user(ioc, (void __user *)arg, tmp)) {//从⽤户空间获取数据管理结构体的初始化值153 kfree(ioc);154 retval = -EFAULT;155break;156 }157158/* translate to spi_message, execute */159 retval = spidev_message(spidev, ioc, n_ioc);//数据传输,这是整个流程中的核⼼160 kfree(ioc);161break;162 }163164 mutex_unlock(&spidev->buf_lock);165 spi_dev_put(spi);166return retval;167 }通过调⽤函数spi->master->setup()来设置SPI模式。

uart8250 手册

uart8250 手册

uart8250 手册
UART8250手册是一份详细介绍UART8250通信协议的参考文档。

UART(通
用异步收发传输器)是一种常用的串行通信接口标准,用于在计算机系统中实现数据的传输和接收。

手册旨在为用户提供UART8250的功能特性、寄存器配置和操
作原理等方面的详细信息,以帮助用户正确使用和配置UART8250。

UART8250手册首先介绍了UART8250的基础知识和通信原理。

它解释了UART的工作模式、数据传输格式和波特率的概念。

这些基本概念将帮助用户了解UART8250的工作方式,并为正确配置和使用UART8250提供基本的理论支持。

随后,手册详细描述了UART8250的功能特性和寄存器配置。

它列出了
UART8250的各个寄存器及其功能,并提供了针对每个寄存器的配置说明。

用户可以根据需求配置这些寄存器以实现所需的数据传输设置,如数据位数、校验位、停止位等。

此外,手册还提供了UART8250的操作指南。

它详细描述了UART8250的数
据传输过程,包括数据的发送和接收。

用户可以根据手册中的操作流程和示例代码,编写自己的应用程序,并与其他设备进行数据交换。

最后,手册包含了常见问题解答和故障排除的相关信息。

如果用户在使用UART8250时遇到问题或故障,可以参考手册中的解答和排除步骤,以快速解决问题并恢复正常的通信功能。

总之,UART8250手册是一份必备的参考文档,用户可以通过阅读手册来了解UART8250的功能特性、配置和操作原理,以实现相关的串行通信任务。

[VIP专享]linux设备驱动之8250串口驱动

[VIP专享]linux设备驱动之8250串口驱动

unreg_uart_drv:
uart_unregister_driver(&serial8250_reg);
out:
return ret;
}
这段代码涉及到的知识要求,如 platform ,uart 等我们_NR:表示串口的个数。这个参数在编译内核的时候可以自己配
= "serial",
.dev_name = "ttyS",
.major
= TTY_MAJOR,
.minor
= 64,
.nr
= UART_NR,
.cons
= SERIAL8250_CONSOLE,
2006年经省农业厅,南平市政1府94批1准年,毛南泽平东农在校《与改建造阳我农们业的工学程习学》校一合文署中办,学把,这强句强原联指合治,学实态行度一的套话班古子为,今两用个,校从区哲的学管的理高体度制做,了从新而的使分学析校,的深办化学了规对模实,事办求学是实的力理都解有,长并足为的其发提历展出史,了的逐一经步个验发经教展典训成的告为注诉有释我着,们广指:泛出什发:么展“时空‘候间实坚和事持良’实好就事发是求展客是前观,景存党的在和闽着国北的家唯一的一切事一事业所物就集,会文第‘顺理一是利、个’发农问就展工题是;商,客什实贸实观么事为事事时求一求物候是体是的背是,地内离一面看部实个向待联事老全我系求话国们,是题招的即,,生学规党实和校律和事就。性国求业职,家是的业‘的一,教求事一语办育’业、,学明就就实出规显是会事自模不我遭求东最同们遇是汉大于去挫地班、高研折看固师等究。待所资教”同学著力育。时校《量和毛,、汉最中泽只学书雄学东有生河厚教对坚和间、育中持学献办,国实校王学不社事当传质同会求前》量点、是工。和就中,作书办在国党以中学于革和及称声职命人存赞誉业的民在刘高教分的的德的育析事问“综所无业题修合有不才学性工贯能好国作穿顺古家和着利,级任实前实重何事进事点事求,求中情是一是专都的旦。和必精背”省须神离其级靠。实意文自因事思明己而求是学完他是根校成才就据。。能必实而找然事这到遭求些中到索成国挫真绩革折理的命甚。取的至得规倒是律退得,。益制实于定事学出求校适是党合是政中马领国克导国思的情主坚的义强路世领线界导方观,针的得政根益策本于,要全指求体导,党中是员国马干革克部命思和走主教向义职胜的工利精的,髓辛实。勤事工求作是和是共中同国努革力命的实结践果经,验但的最高主度要总的结一和条概是括得,益中于国学革校命始和终建坚设持的实经事验求表是明的,原实则事,求可是以是说胜,利坚之持本实,事只求要是坚原持则实是事我求们是学,校我各们项党事就业会健永康远、立稳于定不和败谐之发地展。的重要保证。

8250源码解析

8250源码解析

8250源码解析
8250是一款经典的串行通信芯片,被广泛应用于计算机和其他设备之间的串行通信。

以下是关于8250源码的解析:
8250是一款可编程的串行通信芯片,通过编程可以设置串行通信的波特率、数据位数、停止位数、奇偶校验等参数。

在8250的源码中,主要包含以下几个部分:
1、初始化程序:初始化程序用于设置8250芯片的初始状态,包括控制寄存器的设置、波特率发生器的设置等。

初始化程序通常在系统启动时运行一次,以确保串行通信的正常进行。

2、发送程序:发送程序用于将数据发送到串行通信线上。

在8250的源码中,发送程序将数据写入到发送缓冲区,然后通过控制寄存器启动发送。

发送程序还需要处理发送中断,以确定数据是否已经成功发送。

3、接收程序:接收程序用于从串行通信线上接收数据。

在8250的源码中,接收程序通过控制寄存器启动接收。

当接收到数据时,接收程序会将数据写入到接收缓冲区,并产生一个接收中断。

4、中断处理程序:中断处理程序用于处理接收和发送中断。

在8250的源码中,中断处理程序会检查接收和发送的状态,并执行相应的操作,例如清空缓冲区、启动发送或接收等。

除了以上几个部分,8250的源码还包括一些辅助函数,例如读写控制寄存器、读写数据寄存器等。

这些函数用于操作8250芯片的各种寄存器,确保串行通信的正常进行。

总的来说,8250的源码结构比较清晰,通过初始化程序、发送程序、接收程序和中断处理程序等几个部分,可以完成串行通信的功能。

在实际使用中,需要根据具体的应用场景和需求进行适当的配置和优化。

linux 模拟硬件拔插的命令

linux 模拟硬件拔插的命令

linux 模拟硬件拔插的命令在Linux系统中,模拟硬件拔插主要是通过对设备文件进行操作来实现的。

设备文件位于/dev目录下,可以通过特定的命令与其进行交互,相当于对设备进行操作。

以下是一些常见的模拟硬件拔插的命令。

1. modprobe命令:modprobe是Linux内核加载模块的命令,可以通过插拔驱动来加载/卸载模块。

使用modprobe命令可以模拟硬件的拔插操作。

a.加载模块:`sudo modprobe模块名`,例如:`sudo modprobe usb-storage`加载USB存储设备驱动模块。

b.卸载模块:`sudo modprobe -r模块名`,例如:`sudo modprobe -r usb-storage`卸载USB存储设备驱动模块。

2. lsusb命令:lsusb命令用于列出与USB相关的设备信息,可以用来查看已插入的USB设备。

a.列出已连接的USB设备:`lsusb`,该命令会列出已连接的USB 设备的详细信息,包括设备ID、制造商等。

b.列出指定制造商的USB设备:`lsusb -d制造商ID`,例如:`lsusb -d 0424:`列出VID(Vendor ID)为0424的USB设备。

3. lshw命令:lshw命令用于显示硬件信息,可以通过该命令查看系统中的设备信息。

a.列出所有设备信息:`sudo lshw`,该命令会显示系统中所有的硬件信息,包括CPU、内存、磁盘等。

b.列出指定设备信息:`sudo lshw -class设备类别`,例如:`sudo lshw -class disk`列出磁盘设备的详细信息。

4. udevadm命令:udevadm命令用于管理Linux的udev设备管理器,可以监控设备的动态插拔。

a.监控所有设备的插拔事件:`sudo udevadm monitor`,该命令会实时显示设备的插拔信息。

b.监控指定设备的插拔事件:`sudo udevadm monitor --udev`,例如:`sudo udevadm monitor --udev -s subsystem=block -p`监控磁盘设备的插拔事件。

自己写的intel8250串口芯片驱动

自己写的intel8250串口芯片驱动

CU网友自己写的intel8250串口芯片驱动一:前言串口是一种常用的接口,嵌入式开发环境中,开发板通常都会提供串口与PC相连,方便开发者进行测试。

在较早的网络环境中。

UNIX主机通过串口连moden再接通电话线来连通对方电脑。

类似于今天的telnet。

串口经常用来做远程终端使用。

类似于我们之前分析的终端控制台驱动。

不过,不相同的是,终端驱动的输入数据是从键盘鼠标等I/O外设中来,到显示器上显示。

而串口终端的数据来源跟数据输出都是串口。

对于运行中的进程来说,它不需要知道运行在什么样的终端。

Tty层把终端层给封装起来了. 查找了一相有关PC平台上的8250串口芯片资料,结合之前分析的uart架构自己写了一个串口驱动。

在写驱动的过程中,并没有参考linux自带的8250芯片驱动。

目的是为了在写完之后,和linux 自带的驱动比较,就能发现自己的不足。

在驱动中,按着对端设备的数据模式设定了波特率和数据格式。

并末实现termios库中关于串口参数的设定。

不过在驱动中都写好了接口函数。

按操作接口将其链入即可。

另外:忽略了moden信号的处理。

二:串口的硬件架构在pc中常使用的串口芯片是8250,16450,16450A等。

这些芯片都是从8250发展而来的。

都往下与8250保持兼容。

由于我手头只有8250和16450的详细的资料,代码分析时侧重于这两种类型的芯片分析. 如果有朋友能够提供其它芯片的资料,我会感激不尽^_^.8250提共了9个寄存器。

严格说来,只有8个。

因为其中有两个寄存器是共享同一个寄存器。

各寄存器的作用与寄存位的含义如下表所示:在上图的端口地址标识中,小括号中还有一个地址。

这是因为在PC中。

一般都会有两个串口。

括号外的是主串口的端口地址,而括号里面的是从串口地址。

从上图可以看到8250的寄存器比较繁多,操作比较复杂。

我们依次来看每个寄存器的含义:数据接收寄存器(RBR): 存放接收到的数据。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

linux设备驱动之8250串口驱动一:前言前一段时间自己实践了一下8250芯片串口驱动的编写。

今天就在此基础上分析一下linux kernel自带的串口驱动。

毕竟只有对比专业的驱动代码才能更好的进步,同以往一样,基于linix kernel2.6.25.相应驱动代码位于:linux-2.6.25/drivers/serial/8250.c。

二:8250串口驱动初始化相应的初始化函数为serial8250_init().代码如下:static int __init serial8250_init(void){int ret, i;if (nr_uarts > UART_NR)nr_uarts = UART_NR;printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ ""%d ports, IRQ sharing %sabled\n", nr_uarts,share_irqs ? "en" : "dis");for (i = 0; i < NR_IRQS; i++)spin_lock_init(&irq_lists[i].lock);ret = uart_register_driver(&serial8250_reg);if (ret)goto out;serial8250_isa_devs = platform_device_alloc("serial8250",PLAT8250_DEV_LEGACY);if (!serial8250_isa_devs) {ret = -ENOMEM;goto unreg_uart_drv;}ret = platform_device_add(serial8250_isa_devs);if (ret)goto put_dev;serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);ret = platform_driver_register(&serial8250_isa_driver);if (ret == 0)goto out;platform_device_del(serial8250_isa_devs);put_dev:platform_device_put(serial8250_isa_devs);unreg_uart_drv:uart_unregister_driver(&serial8250_reg);out:return ret;}这段代码涉及到的知识要求,如platform ,uart等我们在之前都已经做过详细的分析。

这里不再重复。

在代码中UART_NR:表示串口的个数。

这个参数在编译内核的时候可以自己配置,默认为32。

我们按照代码中的流程一步一步进行研究。

1:注册uart_driver.对应uart-driver的结构为serial8250_reg.定义如下:static struct uart_driver serial8250_reg = {.owner = THIS_MODULE,.driver_name = "serial",.dev_name = "ttyS",.major = TTY_MAJOR,.minor = 64,.nr = UART_NR,.cons = SERIAL8250_CONSOLE,};TTY_MAJOR定义如下:#define TTY_MAJOR 4从上面可以看出。

串口对应的设备节点为/dev/ ttyS0 ~ /dev/ ttyS0(UART_NR).设备节点号为(4。

64)起始的UART_NR个节点..2:初始化并注册platform_device相关代码如下:serial8250_isa_devs = platform_device_alloc("serial8250", PAT8250_DEV_LEGACY); platform_device_add(serial8250_isa_devs);可以看出。

serial8250_isa_devs.->name为serial8250。

这个参数是在匹配platform_device和platform_driver使用的.3:为uart-driver添加port.相关代码如下:serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)跟进这个函数看一下:static void __initserial8250_register_ports(struct uart_driver *drv, struct device *dev){int i;serial8250_isa_init_ports();for (i = 0; i < nr_uarts; i++) {struct uart_8250_port *up = &serial8250_ports[i];up->port.dev = dev;uart_add_one_port(drv, &up->port);}}在这里函数里,初始化了port.然后将挂添加到uart-driver中。

我们还注意到。

生成的deivce节点,在sysfs中是位于platform_deivce对应目录的下面.serial8250_isa_init_ports()代码如下所示:static void __init serial8250_isa_init_ports(void){struct uart_8250_port *up;static int first = 1;int i;if (!first)return;first = 0;for (i = 0; i < nr_uarts; i++) {struct uart_8250_port *up = &serial8250_ports[i];up->port.line = i;spin_lock_init(&up->port.lock);init_timer(&up->timer);up->timer.function = serial8250_timeout;/** ALPHA_KLUDGE_MCR needs to be killed.*/up->mcr_mask = ~ALPHA_KLUDGE_MCR;up->mcr_force = ALPHA_KLUDGE_MCR;up->port.ops = &serial8250_pops;}for (i = 0, up = serial8250_ports;i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) {up->port.iobase = old_serial_port[i].port;up->port.irq = irq_canonicalize(old_serial_port[i].irq);up->port.uartclk = old_serial_port[i].baud_base * 16;up->port.flags = old_serial_port[i].flags;up->port.hub6 = old_serial_port[i].hub6;up->port.membase = old_serial_port[i].iomem_base;up->port.iotype = old_serial_port[i].io_type;up->port.regshift = old_serial_port[i].iomem_reg_shift;if (share_irqs)up->port.flags |= UPF_SHARE_IRQ;}}在这里,我们关注一下注要成员的初始化。

Uart_port的各项操作位于serial8250_pops中. iobase irq等成员是从old_serial_por这个结构中得来的,这个结构如下所示:static const struct old_serial_port old_serial_port[] = {SERIAL_PORT_DFNS /* defined in asm/serial.h */}#define SERIAL_PORT_DFNS/* UART CLK PORT IRQ FLAGS */{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */从上面看到。

前两项对应了com1 com2的各项参数。

如寄存器首始地址,Irq号等。

后面两项不太清楚。

在上面的代码中,我们看到了uart_port各项成员的初始化。

在后面很多操作中需要用到这个成员。

我们等分析相关部份的时候,再到这个地方来看相关成员的值。

4:注册platform_driver相关代码如下:platform_driver_register(&serial8250_isa_driver);serial8250_isa_driver定义如下:static struct platform_driver serial8250_isa_driver = {.probe = serial8250_probe,.remove = __devexit_p(serial8250_remove),.suspend = serial8250_suspend,.resume = serial8250_resume,.driver = {.name = "serial8250",.owner = THIS_MODULE,},}为了以后把分析集中到具体的驱动部份.我们先把这个platform_driver引会的事件讲述完. 经过前面有关platform的分析我们知道.这个platform的name为” serial8250”.刚好跟前面注册的platform_device相匹配.会调用platform_driver-> probe.在这里,对应的接口为: serial8250_probe().代码如下:static int __devinit serial8250_probe(struct platform_device *dev){struct plat_serial8250_port *p = dev->dev.platform_data;struct uart_port port;int ret, i;memset(&port, 0, sizeof(struct uart_port));for (i = 0; p && p->flags != 0; p++, i++) {port.iobase = p->iobase;port.membase = p->membase;port.irq = p->irq;port.uartclk = p->uartclk;port.regshift = p->regshift;port.iotype = p->iotype;port.flags = p->flags;port.mapbase = p->mapbase;port.hub6 = p->hub6;port.private_data = p->private_data;port.dev = &dev->dev;if (share_irqs)port.flags |= UPF_SHARE_IRQ;ret = serial8250_register_port(&port);if (ret < 0) {dev_err(&dev->dev, "unable to register port at index %d ""(IO%lx MEM%llx IRQ%d): %d\n", i,p->iobase, (unsigned long long)p->mapbase,p->irq, ret);}}return 0;}从上述代码可以看出.会将dev->dev.platform_data所代表的port添加到uart_driver中.这个dev->dev.platform_data究竟代表什么.我们在看到的时候再来研究它.现在,我们把精力集中到uart_port的操作上.三:config_port过程在初始化uart_port的过程中,在以下代码片段:serial8250_isa_init_ports(void){…………for (i = 0, up = serial8250_ports;i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;i++, up++) {up->port.iobase = old_serial_port[i].port;up->port.irq = irq_canonicalize(old_serial_port[i].irq);up->port.uartclk = old_serial_port[i].baud_base * 16;up->port.flags = old_serial_port[i].flags;up->port.hub6 = old_serial_port[i].hub6;up->port.membase = old_serial_port[i].iomem_base;up->port.iotype = old_serial_port[i].io_type;up->port.regshift = old_serial_port[i].iomem_reg_shift;if (share_irqs)up->port.flags |= UPF_SHARE_IRQ;}}而old_serial_port又定义如下:static const struct old_serial_port old_serial_port[] = {SERIAL_PORT_DFNS /* defined in asm/serial.h */};#define SERIAL_PORT_DFNS/* UART CLK PORT IRQ FLAGS */{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */由此可见.port->flags被定义成了STD_COM_FLAGS,定义如下:#ifdef CONFIG_SERIAL_DETECT_IRQ#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ)#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ)#else#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST)#define STD_COM4_FLAGS ASYNC_BOOT_AUTOCONF#endif从这里看到,不管是否自己探测IRQ,都会定义ASYNC_BOOT_AUTOCONF.这样,在uart_add_one_port()的时候.就会进入到port->config_port来配置端口.在8250中,对应的接口为: serial8250_config_port().代码如下:static void serial8250_config_port(struct uart_port *port, int flags){struct uart_8250_port *up = (struct uart_8250_port *)port;int probeflags = PROBE_ANY;int ret;* Find the region that we can probe for. This in turn* tells us whether we can probe for the type of port.*/ret = serial8250_request_std_resource(up);if (ret < 0)return;ret = serial8250_request_rsa_resource(up);if (ret < 0)probeflags &= ~PROBE_RSA;if (flags & UART_CONFIG_TYPE)autoconfig(up, probeflags);if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ)autoconfig_irq(up);if (up->port.type != PORT_RSA && probeflags & PROBE_RSA)serial8250_release_rsa_resource(up);if (up->port.type == PORT_UNKNOWN)serial8250_release_std_resource(up);}serial8250_request_std_resource和serial8250_request_rsa_resource都是分配操作的端口.回顾在前面的分析中.port的相关参数会从old_serial_port中取得.而old_serial_port中又没有定义port->iotype和port-> regshift.也就是说对应这两项全为0.而#define UPIO_PORT (0)即表示是要操作I/O端口.自己阅读这两个函数代表.会发现在serial8250_request_rsa_resource()中是会返回失败的.另外,在uart_add_one_port()在进行端口匹配时,会先置flags为UART_CONFIG_TYPE.这样,在本次操作中, if (flags & UART_CONFIG_TYPE)是会满足的.相应的就会进入autoconfig().代码如下,这段代码比较长,分段分析如下:static void autoconfig(struct uart_8250_port *up, unsigned int probeflags){unsigned char status1, scratch, scratch2, scratch3;unsigned char save_lcr, save_mcr;unsigned long flags;if (!up->port.iobase && !up->port.mapbase && !up->port.membase)return;DEBUG_AUTOCONF("ttyS%d: autoconf (0x%04x, 0x%p): ",up->port.line, up->port.iobase, up->port.membase);* We really do need global IRQs disabled here - we're going to* be frobbing the chips IRQ enable register to see if it exists.*/spin_lock_irqsave(&up->port.lock, flags);up->capabilities = 0;up->bugs = 0;if (!(up->port.flags & UPF_BUGGY_UART)) {/** Do a simple existence test first; if we fail this,* there's no point trying anything else.** 0x80 is used as a nonsense port to prevent against* false positives due to ISA bus float. The* assumption is that 0x80 is a non-existent port;* which should be safe since include/asm/io.h also* makes this assumption.** Note: this is safe as long as MCR bit 4 is clear* and the device is in "PC" mode.*/scratch = serial_inp(up, UART_IER);serial_outp(up, UART_IER, 0);#ifdef __i386__outb(0xff, 0x080);#endif/** Mask out IER[7:4] bits for test as some UARTs (e.g. TL* 16C754B) allow only to modify them if an EFR bit is set.*/scratch2 = serial_inp(up, UART_IER) & 0x0f;serial_outp(up, UART_IER, 0x0F);#ifdef __i386__outb(0, 0x080);#endifscratch3 = serial_inp(up, UART_IER) & 0x0f;serial_outp(up, UART_IER, scratch);if (scratch2 != 0 || scratch3 != 0x0F) {/** We failed; there's nothing here*/DEBUG_AUTOCONF("IER test failed (%02x, %02x) ",scratch2, scratch3);goto out;}}在这里,先对8250是否存在做一个简单的判断.先将IER中的值取得,这样可以在测试之后恢复IER中的值.然后往IER中写放0.再将IER中的值取出.又往IER中写入0xOF.然后再将IER中的值取出.最后将IER中的值恢复到原值.这样就可以根据写入的值和读出的值是否相等来判断该寄存器是否存在.save_mcr = serial_in(up, UART_MCR);save_lcr = serial_in(up, UART_LCR);在这里,先将MCR和LCR中的值取出.因为在后面的操作中会使用这两个寄存器.方便使用完了恢复/** Check to see if a UART is really there. Certain broken* internal modems based on the Rockwell chipset fail this* test, because they apparently don't implement the loopback* test mode. So this test is skipped on the COM 1 through* COM 4 ports. This *should* be safe, since no board* manufacturer would be stupid enough to design a board* that conflicts with COM 1-4 --- we hope!*/if (!(up->port.flags & UPF_SKIP_TEST)) {serial_outp(up, UART_MCR, UART_MCR_LOOP | 0x0A);status1 = serial_inp(up, UART_MSR) & 0xF0;serial_outp(up, UART_MCR, save_mcr);if (status1 != 0x90) {DEBUG_AUTOCONF("LOOP test failed (%02x) ",status1);goto out;}}在这里,将MCR的自检位置位,并允许向中断控制器产生中断.而且产生RTS信号.这样MSR 寄存器应该可以检测到这个信号.如果没有检测到.自测失败!MCR寄存器已经操作完了,恢复MCR寄存器的原值./** We're pretty sure there's a port here. Lets find out what* type of port it is. The IIR top two bits allows us to find* out if it's 8250 or 16450, 16550, 16550A or later. This* determines what we test for next.** We also initialise the EFR (if any) to zero for later. The* EFR occupies the same register location as the FCR and IIR.*/serial_outp(up, UART_LCR, 0xBF);serial_outp(up, UART_EFR, 0);serial_outp(up, UART_LCR, 0);serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO);scratch = serial_in(up, UART_IIR) >> 6;DEBUG_AUTOCONF("iir=%d ", scratch);switch (scratch) {case 0:autoconfig_8250(up);break;case 1:up->port.type = PORT_UNKNOWN;break;case 2:up->port.type = PORT_16550;break;case 3:autoconfig_16550a(up);break;}在这里,先允许使用FIFO寄存器,然后通过IIR寄存的高二位来判断芯片的类型#ifdef CONFIG_SERIAL_8250_RSA/** Only probe for RSA ports if we got the region.*/if (up->port.type == PORT_16550A && probeflags & PROBE_RSA) { int i;for (i = 0 ; i < probe_rsa_count; ++i) {if (probe_rsa[i] == up->port.iobase &&__enable_rsa(up)) {up->port.type = PORT_RSA;break;}}}#endif#ifdef CONFIG_SERIAL_8250_AU1X00/* if access method is AU, it is a 16550 with a quirk */if (up->port.type == PORT_16550A && up->port.iotype == UPIO_AU) up->bugs |= UART_BUG_NOMSR;#endifserial_outp(up, UART_LCR, save_lcr);if (up->capabilities != uart_config[up->port.type].flags) {printk(KERN_WARNING"ttyS%d: detected caps %08x should be %08x\n",up->port.line, up->capabilities,uart_config[up->port.type].flags);}up->port.fifosize = uart_config[up->port.type].fifo_size;up->capabilities = uart_config[up->port.type].flags;up->tx_loadsz = uart_config[up->port.type].tx_loadsz;if (up->port.type == PORT_UNKNOWN)goto out;/** Reset the UART.*/#ifdef CONFIG_SERIAL_8250_RSAif (up->port.type == PORT_RSA)serial_outp(up, UART_RSA_FRR, 0);#endifserial_outp(up, UART_MCR, save_mcr);serial8250_clear_fifos(up);serial_in(up, UART_RX);if (up->capabilities & UART_CAP_UUE)serial_outp(up, UART_IER, UART_IER_UUE);elseserial_outp(up, UART_IER, 0);out:spin_unlock_irqrestore(&up->port.lock, flags);DEBUG_AUTOCONF("type=%s\n", uart_config[up->port.type].name); }最后,复位串口控制器我们假设使用的是8250串口芯片.在芯片类型判断的时候就会进入autoconfig_8250().代码如下:static void autoconfig_8250(struct uart_8250_port *up){unsigned char scratch, status1, status2;up->port.type = PORT_8250;scratch = serial_in(up, UART_SCR);serial_outp(up, UART_SCR, 0xa5);status1 = serial_in(up, UART_SCR);serial_outp(up, UART_SCR, 0x5a);status2 = serial_in(up, UART_SCR);serial_outp(up, UART_SCR, scratch);if (status1 == 0xa5 && status2 == 0x5a)up->port.type = PORT_16450;}如果存在SCR寄存器,则芯片是16450类型的.这不是我们需要研究的芯片.回到serial8250_config_port()中,代码片段如下所示:static void serial8250_config_port(struct uart_port *port, int flags){…………if (flags & UART_CONFIG_TYPE)autoconfig(up, probeflags);if (up->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ)autoconfig_irq(up);if (up->port.type != PORT_RSA && probeflags & PROBE_RSA)serial8250_release_rsa_resource(up);if (up->port.type == PORT_UNKNOWN)serial8250_release_std_resource(up);}如果定义了自己控测IRQ号(CONFIG_SERIAL_8250_DETECT_IRQ).一般情况下,编译内核的时候一般都将其赋值为CONFIG_SERIAL_8250_DETECT_IRQ = y.此时就会进入autoconfig_irq().代码如下:static void autoconfig_irq(struct uart_8250_port *up){unsigned char save_mcr, save_ier;unsigned char save_ICP = 0;unsigned int ICP = 0;unsigned long irqs;int irq;if (up->port.flags & UPF_FOURPORT) {ICP = (up->port.iobase & 0xfe0) | 0x1f;save_ICP = inb_p(ICP);outb_p(0x80, ICP);(void) inb_p(ICP);}/* forget possible initially masked and pending IRQ */probe_irq_off(probe_irq_on());save_mcr = serial_inp(up, UART_MCR);save_ier = serial_inp(up, UART_IER);serial_outp(up, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2);irqs = probe_irq_on();serial_outp(up, UART_MCR, 0);udelay(10);if (up->port.flags & UPF_FOURPORT) {serial_outp(up, UART_MCR,UART_MCR_DTR | UART_MCR_RTS);} else {serial_outp(up, UART_MCR,UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);}serial_outp(up, UART_IER, 0x0f); /* enable all intrs */(void)serial_inp(up, UART_LSR);(void)serial_inp(up, UART_RX);(void)serial_inp(up, UART_IIR);(void)serial_inp(up, UART_MSR);serial_outp(up, UART_TX, 0xFF);udelay(20);irq = probe_irq_off(irqs);serial_outp(up, UART_MCR, save_mcr);serial_outp(up, UART_IER, save_ier);if (up->port.flags & UPF_FOURPORT)outb_p(save_ICP, ICP);up->port.irq = (irq > 0) ? irq : 0;}在上述代码的操作中,先将8250相关中断允许寄存器全打开.然后调用驱动使用的函数, 当它不得不探测来决定哪个中断线被设备在使用. probe_irq_on()将中断暂时关掉,然后配置MCR寄存器使之发送DTR和RTS.之后再用probe_irq_off()来检测IRQ号.如果检测成功,则值赋值给port->irq.进行到这里,conifg_port动作就完成了.经过这个config_port过程后,我们发现,并没有对serial8250_isa_devs->dev-> platform_data赋值,也就是说platform_driver->probe函数并无实质性的处理.在第一次for循环的时,就会因条件不符而退出.四: startup操作在前面分析uart驱动架构的时候,曾说过,在open的时候,会调用port->startup().在本次分析的驱动中,对应接口为serial8250_startup().分段分析如下:static int serial8250_startup(struct uart_port *port){struct uart_8250_port *up = (struct uart_8250_port *)port;unsigned long flags;unsigned char lsr, iir;int retval;up->capabilities = uart_config[up->port.type].flags;up->mcr = 0;if (up->port.type == PORT_16C950) {/* Wake up and initialize UART */up->acr = 0;serial_outp(up, UART_LCR, 0xBF);serial_outp(up, UART_EFR, UART_EFR_ECB);serial_outp(up, UART_IER, 0);serial_outp(up, UART_LCR, 0);serial_icr_write(up, UART_CSR, 0); /* Reset the UART */serial_outp(up, UART_LCR, 0xBF);serial_outp(up, UART_EFR, UART_EFR_ECB);serial_outp(up, UART_LCR, 0);}#ifdef CONFIG_SERIAL_8250_RSA/** If this is an RSA port, see if we can kick it up to the* higher speed clock.*/enable_rsa(up);#endif/** Clear the FIFO buffers and disable them.* (they will be reenabled in set_termios())*/serial8250_clear_fifos(up);上面的代码都不是对应8250芯片的情况/** Clear the interrupt registers.*/(void) serial_inp(up, UART_LSR);(void) serial_inp(up, UART_RX);(void) serial_inp(up, UART_IIR);(void) serial_inp(up, UART_MSR);复位LSR,RX,IIR,MSR寄存器/** At this point, there's no way the LSR could still be 0xff;* if it is, then bail out, because there's likely no UART* here.*/if (!(up->port.flags & UPF_BUGGY_UART) &&(serial_inp(up, UART_LSR) == 0xff)) {printk("ttyS%d: LSR safety check engaged!\n", up->port.line);return -ENODEV;}若LSR寄存器中的值为0xFF.异常/** For a XR16C850, we need to set the trigger levels*/if (up->port.type == PORT_16850) {unsigned char fctr;serial_outp(up, UART_LCR, 0xbf);fctr = serial_inp(up, UART_FCTR) & ~(UART_FCTR_RX|UART_FCTR_TX);serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_RX);serial_outp(up, UART_TRG, UART_TRG_96);serial_outp(up, UART_FCTR, fctr | UART_FCTR_TRGD | UART_FCTR_TX);serial_outp(up, UART_TRG, UART_TRG_96);serial_outp(up, UART_LCR, 0);}16850系列芯片的处理,忽略if (is_real_interrupt(up->port.irq)) {/** Test for UARTs that do not reassert THRE when the* transmitter is idle and the interrupt has already* been cleared. Real 16550s should always reassert* this interrupt whenever the transmitter is idle and* the interrupt is enabled. Delays are necessary to* allow register changes to become visible.*/spin_lock_irqsave(&up->port.lock, flags);wait_for_xmitr(up, UART_LSR_THRE);serial_out_sync(up, UART_IER, UART_IER_THRI);udelay(1); /* allow THRE to set */serial_in(up, UART_IIR);serial_out(up, UART_IER, 0);serial_out_sync(up, UART_IER, UART_IER_THRI);udelay(1); /* allow a working UART time to re-assert THRE */iir = serial_in(up, UART_IIR);serial_out(up, UART_IER, 0);spin_unlock_irqrestore(&up->port.lock, flags);/** If the interrupt is not reasserted, setup a timer to* kick the UART on a regular basis.*/if (iir & UART_IIR_NO_INT) {pr_debug("ttyS%d - using backup timer\n", port->line);up->timer.function = serial8250_backup_timeout;up->timer.data = (unsigned long)up;mod_timer(&up->timer, jiffies +poll_timeout(up->port.timeout) + HZ / 5);}}如果中断号有效,还要进一步判断这个中断号是否有效.具体操作为,先等待8250发送寄存器空.然后允许发送中断空的中断.然后判断IIR寄存器是否收到中断.如果有没有收到中断,则说明这根中断线无效.只能采用轮询的方式.关于轮询方式,我们在之后再以独立章节的形式给出分析/** If the "interrupt" for this port doesn't correspond with any* hardware interrupt, we use a timer-based system. The original* driver used to do this with IRQ0.*/if (!is_real_interrupt(up->port.irq)) {up->timer.data = (unsigned long)up;mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout));} else {retval = serial_link_irq_chain(up);if (retval)return retval;}如果没有设置中断号,则采用轮询方式.如果中断后有效.流程转入serial_link_irq_chain().在这个里面.会注册中断处理函数./** Now, initialize the UART*/serial_outp(up, UART_LCR, UART_LCR_WLEN8);spin_lock_irqsave(&up->port.lock, flags);if (up->port.flags & UPF_FOURPORT) {if (!is_real_interrupt(up->port.irq))up->port.mctrl |= TIOCM_OUT1;} else/** Most PC uarts need OUT2 raised to enable interrupts.*/if (is_real_interrupt(up->port.irq))up->port.mctrl |= TIOCM_OUT2;serial8250_set_mctrl(&up->port, up->port.mctrl);/** Do a quick test to see if we receive an* interrupt when we enable the TX irq.*/serial_outp(up, UART_IER, UART_IER_THRI);lsr = serial_in(up, UART_LSR);iir = serial_in(up, UART_IIR);serial_outp(up, UART_IER, 0);if (lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT) {if (!(up->bugs & UART_BUG_TXEN)) {up->bugs |= UART_BUG_TXEN;pr_debug("ttyS%d - enabling bad tx status workarounds\n",port->line);}} else {up->bugs &= ~UART_BUG_TXEN;}spin_unlock_irqrestore(&up->port.lock, flags);/** Clear the interrupt registers again for luck, and clear the* saved flags to avoid getting false values from polling* routines or the previous session.*/serial_inp(up, UART_LSR);serial_inp(up, UART_RX);serial_inp(up, UART_IIR);serial_inp(up, UART_MSR);up->lsr_saved_flags = 0;up->msr_saved_flags = 0;/** Finally, enable interrupts. Note: Modem status interrupts* are set via set_termios(), which will be occurring imminently* anyway, so we don't enable them here.*/up->ier = UART_IER_RLSI | UART_IER_RDI;serial_outp(up, UART_IER, up->ier);if (up->port.flags & UPF_FOURPORT) {unsigned int icp;/** Enable interrupts on the AST Fourport board*/icp = (up->port.iobase & 0xfe0) | 0x01f;outb_p(0x80, icp);(void) inb_p(icp);}return 0;}最后,就是对8250芯片的初始化了.包括:在LCR中设置数据格式,在MCR中设置允许中断到8259.在IER中设置相关允许位.另外在open的时候,还会调用port-> enable_ms ()接口,在本例中对应为: serial8250_enable_ms().代码如下:static void serial8250_enable_ms(struct uart_port *port){struct uart_8250_port *up = (struct uart_8250_port *)port;/* no MSR capabilities */if (up->bugs & UART_BUG_NOMSR)return;up->ier |= UART_IER_MSI;serial_out(up, UART_IER, up->ier);}即允许moden中断五:数据发送的操作在uart驱动架构中分析过,在发送数据的时候,uart层先会将数据放入circ_buffer.最后再调用port-> start_tx().在这里,这个接口对应为serial8250_start_tx().代码如下:static void serial8250_start_tx(struct uart_port *port){struct uart_8250_port *up = (struct uart_8250_port *)port;if (!(up->ier & UART_IER_THRI)) {up->ier |= UART_IER_THRI;serial_out(up, UART_IER, up->ier);if (up->bugs & UART_BUG_TXEN) {, ; unsigned char lsr, iir;lsr = serial_in(up, UART_LSR);up->lsr_saved_flags |= lsr & LSR_SAVE_FLAGS;iir = serial_in(up, UART_IIR) & 0x0f;if ((up->port.type == PORT_RM9000) ?(lsr & UART_LSR_THRE &&(iir == UART_IIR_NO_INT || iir == UART_IIR_THRI)) :(lsr & UART_LSR_TEMT && iir & UART_IIR_NO_INT))transmit_chars(up);}}/** Re-enable the transmitter if we disabled it.*/if (up->port.type == PORT_16C950 && up->acr & UART_ACR_TXDIS) {up->acr &= ~UART_ACR_TXDIS;serial_icr_write(up, UART_ACR, up->acr);}}这个函数非常简单.如果没有定义发送空中断.则在IER中打开这个中断.关于TXEN上的bug修复和16C950类型的芯片不是我们所关注的部份.那,这里只是打开了这个中断.写数据到芯片的这个过程是在什么地方完成的呢?是在中断处理中.如果是发送空的中断,就将circ buffer中的数据写出发送寄存器.跟踪一下代码.中断处理函数为serial8250_interrupt().static irqreturn_t serial8250_interrupt(int irq, void *dev_id){struct irq_info *i = dev_id;struct list_head *l, *end = NULL;int pass_counter = 0, handled = 0;DEBUG_INTR("serial8250_interrupt(%d)...", irq);spin_lock(&i->lock);l = i->head;do {struct uart_8250_port *up;unsigned int iir;up = list_entry(l, struct uart_8250_port, list);iir = serial_in(up, UART_IIR);if (!(iir & UART_IIR_NO_INT)) {serial8250_handle_port(up);handled = 1;end = NULL;} else if (up->port.iotype == UPIO_DWAPB &&(iir & UART_IIR_BUSY) == UART_IIR_BUSY) {/* The DesignWare APB UART has an Busy Detect (0x07)* interrupt meaning an LCR write attempt occured while the* UART was busy. The interrupt must be cleared by reading* the UART status register (USR) and the LCR re-written. */unsigned int status;status = *(volatile u32 *)up->port.private_data;serial_out(up, UART_LCR, up->lcr);handled = 1;end = NULL;} else if (end == NULL)end = l;l = l->next;if (l == i->head && pass_counter++ > PASS_LIMIT) {/* If we hit this, we're dead. */printk(KERN_ERR "serial8250: too much work for ""irq%d\n", irq);break;}} while (l != end);spin_unlock(&i->lock);DEBUG_INTR("end.\n");return IRQ_RETVAL(handled);}这里可能有个疑问的地方,挂在这个链表上的到底是什么.这我们要从serial_link_irq_chain()来说起.该函数代码如下:static int serial_link_irq_chain(struct uart_8250_port *up){struct irq_info *i = irq_lists + up->port.irq;int ret, irq_flags = up->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0;spin_lock_irq(&i->lock);if (i->head) {list_add(&up->list, i->head);spin_unlock_irq(&i->lock);ret = 0;} else {INIT_LIST_HEAD(&up->list);i->head = &up->list;spin_unlock_irq(&i->lock);ret = request_irq(up->port.irq, serial8250_interrupt,irq_flags, "serial", i);if (ret < 0)serial_do_unlink(i, up);}return ret;}从这里看到,注册中断处理函数的参数i就是对应irq_lists + up->port.irq.即对应在irq_lists数组中的port->irq项.随后,将注册的uart_8250_port添加到了这个链表.奇怪了,为什么要这么做了?我们返回old_serial_port的定义看看:static const struct old_serial_port old_serial_port[] = {SERIAL_PORT_DFNS /* defined in asm/serial.h */};#define SERIAL_PORT_DFNS/* UART CLK PORT IRQ FLAGS */{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */{ 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */{ 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */在这里,注意到同一个IRQ号会对应两个port. IRQ中发生中断的时候,怎么去判断是哪一个port所引起的.当然方法有多种多样.在这里,8250驱动的作者是将不同的port链入到IRQ对应的链表来完成的.这样,如果IRQ产生了中断了,就判断挂在该链表中的port,看中断是否由它产生.经过这个分析之后,我们应该很清楚serial8250_interrupt()中的处理流程了.对应产生IRQ的port,流程会转入serial8250_handle_port()中.代码如下:static inline voidserial8250_handle_port(struct uart_8250_port *up){unsigned int status;unsigned long flags;spin_lock_irqsave(&up->port.lock, flags);status = serial_inp(up, UART_LSR);DEBUG_INTR("status = %x...", status);if (status & UART_LSR_DR)receive_chars(up, &status);check_modem_status(up);if (status & UART_LSR_THRE)transmit_chars(up);spin_unlock_irqrestore(&up->port.lock, flags);}对于产生中断的情况下,判断发送缓存区是否为空,如果为空,就可以发送数据了.对应的处理在transmit_chars(up).如果接收缓存区满,就那接收数据,这是在receive_chars()中处理的.对于接收数据,我们在下一节再分析.transmit_chars()代码如下:static void transmit_chars(struct uart_8250_port *up){struct circ_buf *xmit = &up->->xmit;int count;if (up->port.x_char) {。

相关文档
最新文档