Linux驱动学习笔记(三)
阅读原文时间:2021年04月21日阅读:1

中断

快速中断具有原子性,不允许嵌套;普通中断不具有原子性且允许嵌套

中断共享是指将不同的设备挂在同一条中断线上。

中断注册:

int request_irq(unsignedint irq,void (*handler)(int,void*,struct pt_regs*),unsigned long flags,constchar *devname,void *dev_id)返回0成功,失败返回一个错误码。irq为中断号,handler中断处理函数,flags中断标志(IRQF_DISABLED快速中断,IRQF_SHARED可在设备间共享),devname设备名,dev_id共享中断时使用。

中断处理程序:

运行在中断上下文中。不能向用户空间发送或接受数据;不能使用可能引起阻塞的函数;不能使用可能引起调度的函数。

中断处理程序流程:

Ø 判断是否是本设备产生了中断;inb()

Ø 清除中断标志位;(outb())

Ø 中断处理;

Ø 唤醒等待数据的进程(wait_up_interruptible(&short_queue));

释放中断处理程序:

voidfree_irq(unsigned int irq,)

Linux网路体系结构

采用四层的internet模型

Linux网路子系统从上到下分别是:用户层、系统调用接口层、协议无关接口层、网络协议层、设备无关接口层、设备驱动层、物理层。

设备无关接口层:

设备无关接口将协议与各种网络设备驱动连接在一起。这一层提供一组通用函数供底层网络设备程序使用,让他们对高层协议栈进行操作。首先驱动程序通过register_netdevice()或unregister_netdevice在内核中进行注册或注销。调用前必须先填写net_device结构,然后传递这个结构进行注册,内核调用它的init函数,然后执行一组健全性检查,并将新设备添加到设备列表中(内核中的活动设备链表)。要从协议层向设备发送数据,需要使用dev_queue_xmit函数,这个函数对数据进行排队,并交由底层设备驱动程序进行最终传输报文的,接收通常使用netif_rx执行的。当底层设备驱动程序接收到一个报文(包含在所分配的sk_buff中)时,就会通过调用netif_rx将数据上传置设备无关层,然后,这个函数通过netif_rx_schedule将sk_buff在上层协议队列中进行排队,供以后进行处理。

网卡驱动设计

网络接口设备描述结构体:(net_device)可以有以下函数动态分配

struct net_device *alloc_netdev(int sizeof_priv,constchar *mask,void (*setup)(struct net_device *))sizeof_priv私有数据去大小;mask设备名,setup初始化函数

struct net_device *alloc_etherdev(int sizeof_priv)  

初始化函数,在register_netdev时被调用:

int (*ndo_init)(struct net_device *dev);

打开接口:ifconfig激活时,接口被打开

int  (*ndo_open)(structnet_device *dev);在打开时申请中断DMA等,设置寄存器、启动设备,启动发送队列。

停止接口:

int  (*ndo_stop)(structnet_device *dev);

数据发送函数:

netdev_tx_t   (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);

处理特定于接口的ioctl命令:

int  (*ndo_do_ioctl)(structnet_device *dev,struct ifreq *ifr, int cmd);

改变MAC地址的函数,需要硬件支持:

int  (*ndo_set_mac_address)(struct net_device *dev,void *addr);

网卡注册:

int  register_netdevice(structnet_device *dev);

网卡注销:

void unregister_netdevice(struct net_device *dev)

Linux内核中的每个网络包都有一个套接字缓冲区结构srtuct sk_buff描述,即一个sk_buff结构就是一个包,指向sk_buff的指针通常被称为skb。

Skb操作函数:

分配一个sk_buff结构,供协议栈代码使用:

struct sk_buff *alloc_skb(unsigned int size,gfp_tpriority);

分配一个sk_buff结构,供驱动代码使用:

struct sk_buff *dev_alloc_skb(unsigned int length);

向前移动skb的head指针,并返回head移动之后的值:

unsigned char *skb_put(struct sk_buff *skb, unsigned intlen);

释放skb:

void kfree_skb(struct sk_buff *skb);协议栈代码使用

void dev_kfree_skb(struct sk_buff *skb);驱动代码使用

数据接收流程:

  • 分配skb:skb=dev_alloc_skb(pkt->datalen+2)
  • 从硬件中读取数据到skb
  • 调用netif_rx将数据交给协议栈netif_rx(skb)

网络接口支持三种中断:新报文到达中断、报文发送完成中断、出错中断。中断处理程序可通过查看网卡中的中断寄存器,来分辨出中断类型。

Input输入子系统

分为三部分:驱动、input core、event handler。

Input设备用input_dev结构体描述,使用input子系统实现输入设备驱动时,驱动的核心工作时想系统报告按键、触摸屏、键盘、鼠标等事件(event、通过input_event结构体描述),不在关心文件操作接口,以为input子系统已经完成了文件操作接口。驱动报告事件经过inputcore和eventhandler最终到达用户空间。

注册输入设备:

int input_register_device(struct input_dev *dev);

注销输入设备:

void input_unregister_device(struct input_dev *dev);

驱动实现:

设备驱动程序通过set_bit()告诉input子系统它支持哪些事件。例如:set_bit(EV_KEY,button_dev.evbit)

struct iput_dev中的evbit用来表示设备所支持的实践类型,keybit用来表示设备支持的按键类型。

事件类型:

EV_RST Reset       EV_KEY 按键     EV_REL 相对坐标      EV_ABS 绝对坐标

EV_MSC 其他        EV_LEDLED         EV_SND 声音            EV_BEP repeat

EV_FF 力反馈

用于报告EV_KEY:(code事件代码,value事件值)

void input_report_key(struct input_dev *dev,unsigned intcode,int value);

用于报告EV_REL:

void input_report_rel(struct input_dev *dev,unsigned intcode,int value);

用于报告EV_ABS:

void input_report_abs(struct input_dev *dev,unsigned intcode,int value);

input_sync用于事件同步,表示一次所有报告完毕。

PCI总线

PCI是(Peripheral Component Interconnect外围设备互联)简称。有三大优点:在计算机和外设间传输数据时具有更好的性能;能够尽量独立于具体的平台;可以方便的实现即插即用。

每一个PCI设备由一个总线号、一个设备号、一个功能号确定。PCI规范允许一个系统最多拥有256条总线,每条总线最多带32个设备,每个设备最多8个功能。

每个PCI设备都有一组固定格式的寄存器,即配置寄存器;由Linux内核中的PCI初始化代码与驱动程序共同使用,内核启动时对其进行初始化,包括设置中断号以及I/O基址。

00H-01H Vendor ID 制造商标识;02H-03H Device ID 设备标识;04H-05H Command 命令寄存器

06H-07H Status 状态寄存器;08H Revision ID 版本识别号寄存器;

09H-0BH Class Code 分类代码寄存器;0CH Cache LineSize CACHE行长度寄存器

10H-13H基地址寄存器0;14H-17H基地址寄存器1;18H-1BH基地址寄存器2;1CH-19H基地址寄存器3

20H-23H基地址寄存器4;24H-27H基地址寄存器5;

3H Interrupt Line 中断线寄存器;3DH Interrupt Pin 中断引脚寄存器。

PCI驱动使用struct pci_driver结构来描述

struct pci_driver{

     …………

     const structpci_device_id *id_table;//设备列表

     int(*probe)(struct oci_dev *dev,const struct pci_device_id *id);

     void(*remove)(struct pci_dev *dev);

     …………

};

PCI设备注册函数:

pci_register_driver(struct pci_device *dev);

使能PCI设备;

int pci_enable_device(struct pci_dev *dev);

获取基地址:

pci_resource_start(struct pci_dev *dev,int bar);返回指定区域的起始地址,这个区域由参数bar指定,范围0-5,表示6个PCI区域中的一个。

pci_resource_end(struct pci_dev *dev,int bar);返回指定区域的末地址。