APP下载

Linux系统下的驱动程序开发

2018-11-21韩贤忠胡业火

科学与财富 2018年29期
关键词:驱动程序板卡内核

韩贤忠 胡业火

摘 要: 本文以PC-6325A板卡Linux系统下的驱动开发为例介绍了PC-6325A板卡信息及工作原理,linux内核编译加载、常用函数方法实现等基本编程技术。可为Linux系统下驱动程序开发人员提供一定参考。

关键词: Linux;驱动程序

1 引言

由于Linux系统具有运行安全稳定、功能强大、获取方便等众多优点,逐渐被众多开发人员所使用。但市场上Linux发行版本多,很多硬件设备缺少对应版本的驱动程序,需要自行开发。

本文以PC-6325A板卡开发为例,介绍了linux内核编译加载、常用函数方法实现等基本编程技术,在后续驱动程序开发中可以此为参考。

2 硬件设备分析

PC-6325A模入接口卡适用于具有ISA总线的PC系列微机,具有很好的兼容性,CPU从目前广泛使用的64位处理器到早期的16为处理器均可使用,操作系统也适用于MS-DOS、Windows系列、Unix等多种操作系统以及专业数据采集分析系统labVIEW等软件环境。以下对PC-6325A板卡进行详细介绍。

2.1 工作原理

PC-6325A板卡主要由模拟多路开关电路、放大器电路、模数转换电路、接口控制逻辑电路、光电隔离电路及DC/DC电源电路组成。

2.1.1 模拟多路开关电路

模拟多路开关由4片8选1模拟开关芯片等组成,通过KJ1和KJ2跨接插座可以选择32路单端或16路双端输入方式,并将选中的信号送入差分放大器处理。

2.1.2 模数转换电路

PC-6325A卡选用新一代A/D器件ADS7808作为模数转换器件。ADS7808内部自带采保和精密基准电源。A/D转换可以由程序启动,也可由外部触发信号启动。A/D转换结束标志可以由程序查询检出,也可通过中断方式通知CPU处理。

2.1.3 接口控制逻辑电路及光隔电路

接口控制逻辑电路用来产生与各种操作有关的控制信号。光隔电路采用6N137高速光耦对系统总线与模拟信号之间进行光电隔离,以避免相互间的干扰。

2.1.4 DC/DC电源电路

DC/DC电源电路有电源模块及相关的滤波元件组成。该电源模块的输入电压为+5V,输出电压为与原边隔离的±15V和+5V,原付边之间隔离电压可达1500V。

3 驱动程序设计

3.1 编译内核

设备驱动属于linux内核的部分在编写Linux设备驱动前需要对Linux操作系统内核进行编译。本次PC-6325板卡驱动开发调试采用2.6.23版本内核。

编译步骤如下:

1.下载并解压Linux内核一般内核源码放在/usr/src目录下。

2.清除从前编译内核时残留的.o文件和不必要的关联:

cd /usr/src/linux

make mrproper

確保源代码目录下没有不正确的.o文件和文件依赖关系,执行该命令后,内核选项会回到默认的状态下。如果为下载的内核源码,且为第一次编译可跳过该步骤。

3.配置内核,修改相关参数:

图形界面下,make xconfig;

字符界面下, make menuconfig;

在内核配置菜单中正确设置内核选项保存退出。

4.正确设置关联文件:

make dep

根据上一步所选择的选项,建立文件的依赖关系。

5.编译内核:

对于大内核,make bzlmage

对于小内核,mkae zlmage

6.编译模块:

make modules

编译可加载模块(即内核选项中选择为M的选项)。

7.安装模块:

make modules_install

将编译好的modules拷贝到/lib/modules下(该步骤需要管理员权限)。

3.2 编写驱动程序

内核编译完成后就可以进行驱动程序代码编写了,但在此之前需要了解一下Linux系统的一个基本概念,内核空间和用户空间。模块运行在内核空间用于扩展内核功能,应用程序运行在用户空间。内核空间与用户空间可理解为两种运行模式,有着不同的优先级及自己的内存映射。相应的在代码编写中也会有内核空间编程及用户空间编程的区别。本文将以PC-6325A板卡驱动开发中的具体代码为例对驱动程序中内核空间及用户空间较为常用的函数方法进行详细介绍。

3.2.1 初始化和关闭

1.初始化代码如下:

static int __init pc6325_init (void){

register_chrdev(MAJOR_NUM, "pc6325", &pc6325;_fops);

gPc6325Dev = kmalloc(sizeof(struct pc6325_dev), GFP_KERNEL);

init_MUTEX(&gPc6325Dev-;>sem); /*初始化信号量*/

return 0;

}

module_init(pc6325_init);

初始化函数声明为static,该函数仅在初始化期间使用。模块装载之后,模块装载器将初始化函数丢掉,并释放函数所占用的内存。

module_init用于说明内核初始化函数的位置。如果没有这个定义,初始化函数将无法被调用。

2.清除函数代码如下:

static void __exit pc6325_exit (void){

unregister_chrdev(MAJOR_NUM, "pc6325");

}

module_exit(pc6325_exit);

清除函数在模块移除前注销接口并返回系统中的所有资源。清除函数无返回值,声明为void清除函数用于模块卸载,在模块卸载或者系统关闭时调用。

3.2.2 数据读取与发送

1.读取数据代码如下

static ssize_t pc6325_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)

{

unsigned long p = *ppos;

unsigned int count = size;

int ret = 0;

struct pc6325_dev *dev = filp->private_data; /*获得设备结构体指针*/

if (down_interruptible(&dev-;>sem)) /* 获得信号量 */

{

return - ERESTARTSYS;

}

dev->mem[0]=inb(dev->address+p);

copy_to_user(buf, &(dev->mem[0]), count);

up(&dev-;>sem);

return ret;

}

该函数用来从设备读取数据,函数指针被赋予NULL值时,将导致reed系统调用出错并返回-EINVAL(非法参数)。函数返回非负值表示成功读取的字节数。对于该方法,参数filp是文件指针,参数count是请求传输的数据长度。参数buff是用户空间的指针,指向用户空间的缓冲区,保存要写入的数据,或者是一个存放新读入数据的空缓冲区。最后的offp是一个指向长偏移量类型的对象指针,用于指明用户在文件中进行存取操作的位置。

2.发送数据函数如下:

static ssize_t pc6325_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)

{

unsigned long p = *ppos;

unsigned int count = size;

int ret = 0;

unsigned char vData=0;

struct pc6325_dev *dev = filp->private_data; /* 获得设备结构体指针 */

if( count<3 ){

return;

}

if (down_interruptible(&dev-;>sem)) /* 获得信号量 */

{

return - ERESTARTSYS;

}

copy_from_user(dev->mem+p,buf,count);

if( count==3 && dev->mem[0]==0xED ){

dev->address=dev->mem[1]*0x100+dev->mem[2];

pc6325_set_base_address(dev->address);

}else if( count==3 && dev->mem[0]==0xCD ){

dev->offset=dev->mem[1];

vData=dev->mem[2];

outb(vData,dev->address+dev->offset);

}

up(&dev-;>sem); /* 釋放信号量 */

return ret;

}

向设备发送数据。如果返回值非负,则表示成功写入的字节数。其用法及参数与read方法一致,在此不再赘述。

3.2.3 open和release方法

1.open方法:

int pc6325_open(struct inode *inode, struct file *filp)

{

filp->private_data = gPc6325Dev;

return 0;

}

Open方法提供给驱动程序初始化的能力,从而为以后的操作完成初始化的准备。在多数驱动程序中,open完成如下工作:

a)检查设备特定的错误;

b)如果设备是首次打开,则对其进行初始化;

c)必要时,更新f_op指针;

d)非配并填写置于filp->private_data里的数据结构[1]。

2.release方法:

int pc6325_release(struct inode *inode, struct file *filp)

{

struct pc6325_dev *dev = filp->private_data;

pc6325_release_base_address(dev->address);

//printk(KERN_ALERT "address release");

return 0;

}

release方法的作用于open正好相反。该方法通常完成以下任务。

a)释放有open分配的、保存在filp->private_data中的所有内容;

b)在最后一次关闭操作时关闭设备。

3.2.4 file_operations结构

代码如下:

struct file_operations pc6325_fops={

read: pc6325_read,

write: pc6325_write,

open: pc6325_open,

release: pc6325_release,

owner: THIS_MODULE,

};

file_operations结构用于建立驱动程序与设备编号之间的连接。主要用于实现系统调用。结构中的每一个字段都指向驱动程序中特定操作的函数,对于不支持的操作,对应的字段可设置为NULL值。

3.2.5 模块加载与卸载

1. 模块加载

# Create the device nodes (up to 10 by default)

echo -n "Creating device nodes........... "

rm -f ${path}/${name}*

mknod ${path}/${name} c $major 226

#mknod ${path}/${name} c 226 0

# Create additional nodes for non-service driver

if [ "${bServiceDriver}" == "0" ]; then

mknod ${path}/${name}-0 c $major 000

mknod ${path}/${name}-1 c $major 001

mknod ${path}/${name}-2 c $major 002

mknod ${path}/${name}-3 c $major 003

mknod ${path}/${name}-4 c $major 004

mknod ${path}/${name}-5 c $major 005

mknod ${path}/${name}-6 c $major 006

mknod ${path}/${name}-7 c $major 007

mknod ${path}/${name}-8 c $major 008

mknod ${path}/${name}-9 c $major 009

fi

chmod 777 $path

上述代码中rm -f ${path}/${name}*的作用為卸载之前的模块以释放空间在模块卸载中也会用到,之后的代码为进行模块加载。其中mknod命令用于创建设备文件。最后的chmod 777 $path表示赋予读、写以及运行的权限。

2.模块卸载

echo -n "Clear existing device nodes..... "

rm -f $path/${name}*

echo "Ok (${path}/${name})"

卸载模块代码较为简单使用rm -f $path/${name}*指令卸载即可。

4.结论

Linux系统对于开发人员而言有着众多的优点,但市场上Linux发行版本多,很多硬件设备缺少对应版本的驱动程序,需要自行开发。本文以PC-6325A板卡驱动开发为例介绍了PC-6325A板卡信息及工作原理,linux内核编译加载、常用函数方法实现等基本编程技术。可为Linux系统驱动开发人员提供参考。

参考文献

[1]LINUX设备驱动程序第三版,中国电力出版社,2006年1月,魏永明,耿岳,钟书毅 译.

[2]PC-6325A板卡用户手册.

猜你喜欢

驱动程序板卡内核
强化『高新』内核 打造农业『硅谷』
基于嵌入式Linux内核的自恢复设计
Linux内核mmap保护机制研究
基于PCI9054的多总线通信板卡的研制
基于FPGA的多通道模拟量采集/输出PCI板卡的研制
计算机硬件设备驱动程序分析
微生物内核 生态型农资
一种基于光纤数据传输的多板卡软件程序烧写技术
一种通用模拟量及开关量信号采集板卡的设计
基于MPC8280的CPU单元与内部总线驱动程序设计