驱动程序编译
A. 如何编译wince6.0流驱动
最近开始尝试写WinCE6.0的驱动,当然从最简单的流驱动开始,选择了GPIO的驱动进行实验。本文参考了网上有很多流驱动的开发资料,但在开发的过程中也发现了一些细节问题,网络上并没有给出详细的解答,所以在这里记录下来,并对流驱动开发中的一些问题做了总结。 流驱动的开发有两种方法:添加驱动到BSP和借助驱动调试助手。 第一种,添加驱动到BSP。修改BSP,将驱动加入到BSP当中,再选择该BSP当做OS下载到目标板上。下面以6410下实现GPIO为例,说明详细的步骤: 首先,在\WINCE600\PLATFORM\SMDK6410\SRC\DRIVES目录下面创建文件夹,命名为GPIO,并在GPIO文件夹下面创建源代码文件,命名为gpio.c。同时在DRIVES目录下修改dirs文件,在dirs文件最后添加新建的目录名GPIO。在gpio.c文件中实现的流式接口函数如下(这些接口函数的参数介绍见博文最后):BOOL WINAPI DllEntry(HANDLE hInstDll, DWORD dwReason, LPVOID lpvReserved)DWORD GPI_Init(LPCTSTR PContext, LPCVOID lpvBuscontext)BOOL GPI_Deinit(DWORD hDeviceContext)DWORD GPI_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD shareMode)BOOL GPI_Close(DWORD hOpenContext)DWORD GPI_Read(DWORD hOpenContext, LPVOID pBuff, DWORD Count)DWORD GPI_Write(DWORD hOpenContext, LPVOID pBuff, DWORD Count)DWORD GPI_Seek(DWORD hOpenContext, long Amount, WORD Type)void GPI_PowerUp(DWORD hDeviceContext)void GPI_PowerDown(DWORD hDeviceContext)BOOL GPI_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwactualOut ) 其次,在GPIO文件夹下面创建gpio.def文件,定义需要输出的函数,这些函数能够被其它代码用动态加载的方法调用。具体内容如下,有的介绍里面导出的函数中还有EntryDll,其实没有必要,它是一个入口函数而已,主要导出那些会被调用的就可以了。LIBRARY GPIOEXPORTS GPI_Init GPI_Deinit GPI_Open GPI_Close GPI_Read GPI_Write GPI_Seek GPI_PowerDown GPI_PowerUpGPI_IOControl再次,在GPIO文件夹下面创建makefile文件,具体内容如下,这一步对于不同的流驱动基本不变。!INCLUDE $(_MAKEENVROOT)\makefile.def 接下来,在GPIO文件夹下面创建sources文件,具体内容如下,各个宏的意义不做详细介绍,也可以添加其他的一些宏,根据具体情况定。WINCEOEM=1TARGETNAME=GPIOTARGETTYPE=DYNLINKRELEASETYPE=PLATFORMDEFFILE=gpio.defDLLENTRY=DllEntryTARGETLIBS= \ $(_SYSGENSDKROOT)\lib\$(_CPUINDPATH)\coredll.libSOURCES=gpio.c第五步,修改注册表信息了,打开WINCE600\PLATFORM\SMDK6410\FILES\Platform.reg 文件,添加以下内容:[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\GPIO]"Prefix"="GPI""Dll"="GPIO.Dll""Index"=dword:1"Order"=dword:1这里要注意添加的时候不要添加到带有条件编译的语句当中去,否则还需要设置编译条件,比较麻烦。最后,修改Platform.bib 文件,将GPIO驱动加载到NK当中。打开WINCE600\PLATFORM\SMDK6410\FILES\Platform.bib 文件,添加以下内容,GPIO.dll $(_FLATRELEASEDIR)\GPIO.dll NK SH到这里基本上准备工作就已经完成了,接下来编译,下载到目标板上。之后可以用调试工具查看驱动是否已经成功加入到注册表中,还有驱动是否被OS成功加载了。上述正确后,就可以编写上层测试代码进行验证了。 上述的步骤基本和网络的介绍一致,在调试过程中,遇到了以下两个问题:1、驱动信息已经加入到注册表中,但是NK总是没有加载成功GPIO.dll驱动?解决:出现这种情况,有可能是一下原因造成的:(1)没有实现上面提到的全部的流驱动接口函数,只是实现了其中的一部分,只要补齐所有的驱动接口函数就可以了;(2)在修改Platform.bib文件时,在文件末尾添加的内容。应该在宏FILES前面添加内容,否则就会无法加载驱动了。也有可能是其他原因,本人暂时没有遇到。2、驱动加载后立马又被卸载?解决:本人在调试过程中,通过串口输出信息,发现驱动GPIO被加载后立马又被卸载了,并且调用了GPI_Init函数,后来发现时由GPI_Init的返回值引起的。GPI_Init函数是驱动成功加载后调用的第一个函数,设备管理器通过调用GPI_Init初始化硬件,分配自己的内存空间,并将此内存块的地址以一个DWORD值返回给上层。如果返回0,说明初始化失败,之前分配的系统资源将全部释放,也就是驱动会被卸载。在简单的输出信息的流驱动中,最好将所有的流接口都设置为返回成功。 快速编译技巧:上面的方法要求每次都重新编译内核,很耗费时间,这里收集了网络上提供的修改驱动后快速编译的方法,下面的步骤建立在已经进行过一次上面的操作了,否则必须先修改注册表和.bib文件:(1)在VS2005下Build菜单选择“Open Release Directory in Build Window”,进入到命令行模式,在命令行中进入到你的驱动目录,执行build命令就OK了。其实也可以直接在右侧“Solution Explorer”中找到你的驱动目录,右键选择Build就可以了。(2)在VS2005下Build菜单选择“Make Run-Time Image”就可以产生NK文件了。第二种,使用串口助手调试,这里给出原创的连接we-hjb的BLOG。该方法将会比第一种方法效率高,不需要每次都编译NK,然后下载镜像到目标板,但存在适用范围的问题,即该方法并不是适用于任何的驱动。驱动调试助手,是用来动态管理流驱动。本地驱动和USB驱动不再它的控制范围之内,各位在使用时注意这一点。 补充流接口函数介绍(转自网络):DllEntry(HINSTANCE DllInstance, INT Reason, LPVOID Reserved )这个函数是动态链接库的入口,每个动态链接库都需要输出这个函数,它只在动态库被加载和卸载时被调用,也就是设备管理器调用LoadLibrary而引起它被装入内存和调用UnloadLibrary将其从内存释放时被调用,因而它是每个动态链接库最早被调用的函数,一般用它做一些全局变量的初始化。
参数:
DllInstance:DLL的句柄,与一个EXE文件的句柄功能类似,一般可以通过它在得到DLL中的一些资源,例如对话框,除此之外一般没什么用处。
Reason:一般我们只关心两个值:DLL_PROCESS_ATTACH与DLL_PROCESS_DETACH,Reason等于前者是动态库被加载,等于后者是动态库被释放。
所以,我们可以在Reason等于前者是初始化一些资源,等于后者时将其释放。
DWORD XXX_Init(LPCTSTR pContext,LPCVOID lpvBusContext);它是驱动程序的动态库被成功装载以后第一个被调用的函数。其调用时间仅次与DllEntry,而且,当一个库用来生成多于一个的驱动程序实例时仅调用一次DllEntry,而xxx_Init会被调用多次。驱动程序应当在这个函数中初始化硬件,如果初始化成功,就分配一个自已的内存空间(通常用结构体表示),将自已的状态保存起来,并且将此内存块的地址做为一个DWORD值返回给上层。设备管理器就会用在调用XXX_Open时将此句柄传回,我们就能访问自已的状态。如果初始化失败,则返回0以通知这个驱动程序没有成功加载,先前所分配的系统资源应该全部释放,此程序的生命即告终至。当这个函数成功返回,设备管理器对这个程序就不做进一步处理,除非它设置了更多的特性。至此一个各为XXX的设备就已经加载成功,当用户程序调用CreateFile来打开这个设备时,设备管理器就会调XXX_Open函数。参数:pContext:系统传入的注册表键,通过它可以讲到我们在注册表中设置的配置信息。lpvBusContext:一般不用。实际上,很多程序中将这个函数写成了DWORD XXX_Init(DWORD pContext ),我们只需要将pContext转化成LPCTSTR即可。DWORD XXX_Open(DWORD hDeviceContext,DWORD dwAccess, DWORD dwShareMode);当用户程序调用CreateFile打开这个设备时,设备管理器就会调用此驱动程序的XXX_Open函数。参数:hDeviceContext XXX_Init 返回给上层的值,也就是我们在XXX_Init中分配的用来记录驱动程序信息的那个结构体的指针,我们可以在这个函数中直接将其转化成所定义的结构,从而获取驱动程序的信息。dwAccess 上层所要求的访问方式,可以是读或者写,或者是0,即不读也不写。dwShareMode 上层程序所请求的共享模式,可以是共享读、共享写这两个值的逻辑或,或者是0,即独占式访问。系统层对设备文件的存取权限及共享方法已经做了处理,所以在驱动程序中对这两个参数一般可以不用理会。这个函数一般不用做太多处理,可以直接返回hDeviceContext表示成功,对于一个不支持多个用户的硬件,在设备已经打开后,应该总是返回0以至失败,则CreateFile调用不成功。DWORD XXX_Close( DWORD hDeviceContext); 当用户程序调用CloseHandle关闭这个设备句柄时,这个函数就会被设备管理器调用。参数:hDeviceContext 为XXX_Open返回给上层的那个值。这个函数应该做与XXX_Open相反的事情,具体包括:释放XXX_Open分配的内存,将驱动程序被打开的记数减少等。DWORD XXX_Deinit(DWORD hDeviceContext);这个函数在设备被卸载时被调用,它应该实现与XXX_Init相反的操作,主要为释放前者占用的所有系统资源。参数:hDeviceContext XXX_Init函数返回给上层的那个句柄u void XXX_PowerUp( DWORD hDeviceContext );void XXX_PowerDown(DWORD hDeviceContext );正如其名称中体现的那样,这两个函数在系统PowerUp与PowerDown时被调用,这两个函数中不能使用任何可能引起线程切换的函数,否则会引起系统死机。所以,在这两个函数中,实际上几乎是什么做不了,一般在PowerDown时做一个标志,让驱动程序知道自已曾经被Power Down过。在Power Down/On的过程中硬件可能会掉电,所以,尽管Power On以后,原来的IO操作仍然会从接着执行,但可能会失败。这时,当我们发现一次IO操作失败是因为程序曾经进入过Power Down状态,就重新初始化一次硬件,再做同样的IO操作。BOOL XXX_IOControl(DWORD hDeviceContext,DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut );几乎可以说一个驱动程序的所有功能都可以在这个函数中实现。对于一类CE自身已经支持的设备,它们已经被定义了一套IO操作定,我们只需按照各类设备已经定义的内容去实现所有的IO操作。但当我们实现一个自定义的设备时,我们就可以随心所欲定义我们自已的IO操作。参数:hDeviceContext XXX_Open返回给上层的那个句柄,即我们自已定义的,用来存放程序所有信息的一个结构。dwCode IO操作码,如果是CE已经支持的设备类,就用它已经定义好码值,否则就可以自已定义。pBufIn 传入的Buffer,每个IO操作码都会定义自已的Buffer结构dwLenIn pBufIn以字节记的大小 pBufOut,dwLenOut分别为传出的Buffer,及其以字节记的大小pdwActualOut 驱动程序实际在pBufOut中填入的数据以字节记的大小其中,前两个参数是必须的,其它的任何一个都有可能是NULL或0。所以,当给pdwActualOut赋值时应该先判断它是否为一个有效的地址。本文出自 “飞雪待剑” 博客
B. 如何编译一个linux下的驱动模块
linux下编译运行驱动
嵌入式linux下设备驱动的运行和linux x86 pc下运行设备驱动是类似的,由于手头没有嵌入式linux设备,先在vmware上的linux上学习驱动开发。
按照如下方法就可以成功编译出hello world模块驱动。
1、首先确定本机linux版本
怎么查看Linux的内核kernel版本?
'uname'是Linux/unix系统中用来查看系统信息的命令,适用于所有Linux发行版。配合使用'uname'参数可以查看当前服务器内核运行的各个状态。
#uname -a
Linux whh 3.5.0-19-generic #30-Ubuntu SMPTue Nov 13 17:49:53 UTC 2012 i686 i686 i686 GNU/Linux
只打印内核版本,以及主要和次要版本:
#uname -r
3.5.0-19-generic
要打印系统的体系架构类型,即的机器是32位还是64位,使用:
#uname -p
i686
/proc/version 文件也包含系统内核信息:
# cat /proc/version
Linux version 3.5.0-19-generic(buildd@aatxe) (gcc version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) ) #30-UbuntuSMP Tue Nov 13 17:49:53 UTC 2012
发现自己的机器linux版本是:3.5.0-19-generic
2、下载机器内核对应linux源码
到下面网站可以下载各个版本linux源码https://www.kernel.org/
如我的机器3.5.0版本源码下载地址为:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.5.tar.bz2
下载完后,找一个路径解压,如我解压到/linux-3.5/
然后很重要的一步是:执行命令uname -r,可以看到Ubuntu的版本信息是3.5.0-19-generic
。进入linux源码目录,编辑Makefile,将EXTRAVERSION = 修改为EXTRAVERSION= -19-generic。
这些都是要配置源码的版本号与系统版本号,如果源码版本号和系统版本号不一致,在加载模块的时候会出现如下错误:insmod: error inserting 'hello.ko': -1 Invalid mole format。
原因很明确:编译时用的hello.ko的kenerl 不是我的pc的kenerl版本。
执行命令cp /boot/config-3.5.0-19-generic ./config,覆盖原有配置文件。
进入linux源码目录,执行make menuconfig配置内核,执行make编译内核。
3、写一个最简单的linux驱动代码hello.c
/*======================================================================
Asimple kernel mole: "hello world"
======================================================================*/
#include <linux/init.h>
#include <linux/mole.h>
MODULE_LICENSE("zeroboundaryBSD/GPL");
static int hello_init(void)
{
printk(KERN_INFO"Hello World enter\n");
return0;
}
static void hello_exit(void)
{
printk(KERN_INFO"Hello World exit\n ");
}
mole_init(hello_init);
mole_exit(hello_exit);
MODULE_AUTHOR("zeroboundary");
MODULE_DESCRIPTION("A simple HelloWorld Mole");
MODULE_ALIAS("a simplestmole");
4、写一个Makefile对源码进行编译
KERN_DIR = /linux-3.5
all:
make-C $(KERN_DIR) M=`pwd` moles
clean:
make-C $(KERN_DIR) M=`pwd` clean
obj-m += hello.o
5、模块加载卸载测试
insmod hello.ko
rmmod hello.ko
然后dmesg|tail就可以看见结果了
最后,再次编译驱动程序hello.c得到hello.ko。执行insmod ./hello.ko,即可正确insert模块。
使用insmod hello.ko 将该Mole加入内核中。在这里需要注意的是要用 su 命令切换到root用户,否则会显示如下的错误:insmod: error inserting 'hello.ko': -1 Operation not permitted
内核模块版本信息的命令为modinfo hello.ko
通过lsmod命令可以查看驱动是否成功加载到内核中
通过insmod命令加载刚编译成功的time.ko模块后,似乎系统没有反应,也没看到打印信息。而事实上,内核模块的打印信息一般不会打印在终端上。驱动的打印都在内核日志中,我们可以使用dmesg命令查看内核日志信息。dmesg|tail
可能还会遇到这种问题insmod: error inserting 'hello.ko': -1 Invalid mole format
用dmesg|tail查看内核日志详细错误
disagrees about version of symbolmole_layout,详细看这里。
http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmoles/index.html
在X86上我的办法是:
make -C/usr/src/linux-headers-3.5.0-19-generic SUBDIRS=$PWD moles
C. 驱动程序编译为啥出错
文件不存在或者目录不对,还有种就是没有写入访问读取权限,所以导致编译出错
D. 如何编译驱动程序
驱动的编译和上层应用程序的编译完全不同,作为初学者应该先了解一下,即使你还不懂得怎么写驱动程序。
首先安装DDK,然后随便找一个例子来测试。在菜单中找到BUILD环境菜单执行,不同的系统要使用不同的BUILD环境。会打开一个DOS窗口,这时CD到那个例子程序,输入 build –cZ回车就可以了。 驱动程序都是用一个由DDK提供的叫build.exe的工具编译的。此程序以一个名为SOURCES的文件作为输入,该文件中包含目标可执行文件的名称、类型和要创建的可执行文件的路径,注意这个文件没有后缀名。
SOURCES的文件格式:
TARGETNAME=drivername ,
- 本参数用于指定生成的设备驱动程序名称(不需后缀名),所产生的文件
- 为drivername.sys.
TARGETPATH=./lib
- 本参数用于指定生成的设备驱动程序所存放的路径. 一般采用./lib.
TARGETTYPE=DRIVER
- build能够生成许多不同的目标对象,设备驱动程序一般选用 DRIVER.
INCLUDES=path1;path2;...
- 本参数是可选的, 用于指定其他的#include文件的搜索路径.
TARGETLIBS=lib1;lib2;...
- 本参数是可选的, 用于指定其他的lib库文件的搜索路径.
SOURCES=file1.c file2.c ...
- 本参数用于指定需被编译的全部源文件名称, 后缀名不能省略,文件名之间用空格分开.
SOURCES文件是必需的,如果没有它则表示没有任何源文件需要编译。
如果要换行可以用 ‘/’ 符号,表示对上一行的继续。
也可以创建DIRS文件,DIRS文件用于指定在当前目录下必须创建的子目录。
DIRS文件格式:
DIRS文件的内容由一系列用空格分开的目录名组成
DIRS = /
subdir1 /
subdir2 /
subdir3
DIRS文件是可选的。
有的时候,会提示找不到依赖的文件(.h,.lib 之类),其实设置好 source 文件的
INCLUDES和TARGETLIBS就可以,我第一次编译时就碰到这个问题,和VC环境区别较大,但习惯就好。
E. 如何使用ubuntu来编译驱动
工具/原料
Ubuntu12.04操作系统和测试驱动程序(beep_arv.c)
方法/步骤
在介绍2种方法前,必须知道的知识点:
1.关联文件Makefile:
Makefile:分布在Linux内核源代码中的Makefile用于定义Linux内核的编译规则;
2.管理文件Kconfig:
给用户提供配置选择的功能;
配置工具:
1)包括配置命令解析器;
2)配置用户界面;menuconfig || xconfig;
3)通过脚本语言编写的;
3.
---tristate 代表三种状态:1.[ ]不选择,2.[*]选择直接编译进内核,加载驱动到内核里,3.[m]动态加载驱动;
---bool 代表两种状态,1.[ ]不选择,2.[*]选择;
---"Mini2440 mole sample"这个是在make menuconfig时刷出的提示字符;
---depends on MACH_MINI2440 这个配置选项出现在make menuconfig菜单栏下,在内核配置中必须选中、MACH_MINI2440;
---default m if MACH_MINI2440 这个如果选中了MACH_MINI2440,默认是手
动加载这个驱动;
help:提示帮助信息;
在了解了基本的知识点,便开始进行第一种添加驱动的方法,本次交流是以beep_arv.c蜂鸣驱动程序为基础的
方法一:
1)进入内核的驱动目录;
#cp beep_arv.c /XXX/.../linux-XXXl/drivers/char
2)进入Kconfig添加驱动信息;
#cd /XXX/linux-XXX/.../drivers/char
#vim Kconfig
添加基本信息:
config BEEP_MINI2440
tristate "---HAH--- BEEP"
default
help
this is test makefile!
3)进入Makefile添加驱动编译信息;
#vim Makefile
添加基本信息:
obj-$(CONFIG-BEEP_MINI2440) +=beep_drv.o
方法一结果:
在--Character devices下就能看到配置信息了;
方法二:
1)进入驱动目录,创建BEED目录;
#cd /XXX/.../linux-XXX/drivers/char
#mkdir beep
2)将beep_arv.c驱动程序复制到新建目录下;
#cp beep_arv.c /XXX/.../linux-XXXl/drivers/char/beep
3)创建Makefile和Kconfig文件
#cd char/beep
#mkdir Makefile Kconfig
#chmod 755 Makefile
#chmod 755 Kconfig
4)进入Kconfig添加驱动信息;
#vim Kconfig
添加基本信息:
config BEEP_MINI2440
tristate "---HAH--- BEEP"
default
help
this is test makefile!
5)进入Makefile添加驱动编译信息;
#vim Makefile
添加基本信息:
obj-$(CONFIG_BEEP_MINI2440) +=beep_drv.o
6)并且要到上一级目录的Makefile和Kconfig添加驱动信息;
#cd ../
#vim Makefile
#vim Kconfig
F. 如何编写驱动程序
代码:
#include<linux/mole.h>
#include<linux/kernel.h>
#include<asm/io.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<asm/uaccess.h>
//流水灯代码
#define GPM4CON 0x110002e0
#define GPM4DAT 0x110002e4
static unsigned long*ledcon=NULL;
static unsigned long*leddat=NULL;
//自定义write文件操作(不自定义的话,内核有默认的一套文件操作函数)
static ssize_t test_write(struct file*filp,const char __user*buff,size_t count,loff_t*offset)
{
int value=0;
int ret=0;
ret=_from_user(&value,buff,4);
//底层驱动只定义基本操作动作,不定义功能
if(value==1)
{
*leddat|=0x0f;
*leddat&=0xfe;
}
if(value==2)
{
*leddat|=0x0f;
*leddat&=0xfd;
}
if(value==3)
{
*leddat|=0x0f;
*leddat&=0xfb;
}
if(value==4)
{
*leddat|=0x0f;
*leddat&=0xf7;
}
return 0;
}
//文件操作结构体初始化
static struct file_operations g_tfops={
.owner=THIS_MODULE,
.write=test_write,
};
//杂设备信息结构体初始化
static struct miscdevice g_tmisc={
.minor=MISC_DYNAMIC_MINOR,
.name="test_led",
.fops=&g_tfops,
};
//驱动入口函数杂设备初始化
static int __init test_misc_init(void)
{
//IO地址空间映射到内核的虚拟地址空间
ledcon=ioremap(GPM4CON,4);
leddat=ioremap(GPM4DAT,4);
//初始化led
*ledcon&=0xffff0000;
*ledcon|=0x00001111;
*leddat|=0x0f;
//杂设备注册函数
misc_register(&g_tmisc);
return 0;
}
//驱动出口函数
static void __exit test_misc_exit(void)
{
//释放地址映射
iounmap(ledcon);
iounmap(leddat);
}
//指定模块的出入口函数
mole_init(test_misc_init);
mole_exit(test_misc_exit);
MODULE_LICENSE("GPL");
(6)驱动程序编译扩展阅读:
include用法:
#include命令预处理命令的一种,预处理命令可以将别的源代码内容插入到所指定的位置;可以标识出只有在特定条件下才会被编译的某一段程序代码;可以定义类似标识符功能的宏,在编译时,预处理器会用别的文本取代该宏。
插入头文件的内容
#include命令告诉预处理器将指定头文件的内容插入到预处理器命令的相应位置。有两种方式可以指定插入头文件:
1、#include<文件名>
2、#include"文件名"
如果需要包含标准库头文件或者实现版本所提供的头文件,应该使用第一种格式。如下例所示:
#include<math.h>//一些数学函数的原型,以及相关的类型和宏
如果需要包含针对程序所开发的源文件,则应该使用第二种格式。
采用#include命令所插入的文件,通常文件扩展名是.h,文件包括函数原型、宏定义和类型定义。只要使用#include命令,这些定义就可被任何源文件使用。如下例所示:
#include"myproject.h"//用在当前项目中的函数原型、类型定义和宏
你可以在#include命令中使用宏。如果使用宏,该宏的取代结果必须确保生成正确的#include命令。例1展示了这样的#include命令。
【例1】在#include命令中的宏
#ifdef _DEBUG_
#define MY_HEADER"myProject_dbg.h"
#else
#define MY_HEADER"myProject.h"
#endif
#include MY_HEADER
当上述程序代码进入预处理时,如果_DEBUG_宏已被定义,那么预处理器会插入myProject_dbg.h的内容;如果还没定义,则插入myProject.h的内容。
G. 字符型设备驱动如何编译
字符设备驱动程序框架
1、写出open、write函数
2、告诉内核
1)、定义一个struct file_operations结构并填充好
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_mole变量 */
.open = first_drv_open,
.write = first_drv_write,
};
2)、把struct file_operations结构体告诉内核
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
相关参数:第一个,设备号,0自动分配主设备号,否则为主设备号0-255
第二个:设备名
第二个:struct file_operations结构体
4)、register_chrdev由谁调用(入口函数调用)
static int first_drv_init(void)
5)、入口函数须使用内核宏来修饰
mole_init(first_drv_init);
mole_init会定义一个结构体,这个结构体里面有一个函数指针指向first_drv_init这个函数,当我们加载或安装一个驱动时,内核会自动找到这个结构体,然后调用里面的函数指针,这个函数指针指向first_drv_init这个函数,first_drv_init这个函数就是把struct file_operations结构体告诉内核
6)、有入口函数就有出口函数
mole_exit(first_drv_exit);
最后加上协议
MODULE_LICENSE("GPL");
3、mdev根据系统信息自动创建设备节点:
每次写驱动都要手动创建设备文件过于麻烦,使用设备管理文件系统则方便很多。在2.6的内核以前一直使用的是devfs,但是它存在许多缺陷。它创建了大量的设备文件,其实这些设备更本不存在。而且设备与设备文件的映射具有不确定性,比如U盘即可能对应sda,又可能对应sdb。没有足够的主/辅设备号。2.6之后的内核引入了sysfs文件系统,它挂载在/sys上,配合udev使用,可以很好的完成devfs的功能,并弥补了那些缺点。(这里说一下,当今内核已经使用netlink了)。
udev是用户空间的一个应用程序,在嵌入式中用的是mdev,mdev在busybox中。mdev是udev的精简版。
首先在busybox中添加支持mdev的选项:
Linux System Utilities --->
[*] mdev
[*] Support /etc/mdev.conf
[*] Support subdirs/symlinks
[*] Support regular expressions substitutions when renaming device
[*] Support command execution at device addition/removal
然后修改/etc/init.d/rcS:
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
执行mdev -s :以‘-s’为参数调用位于 /sbin目录写的mdev(其实是个链接,作用是传递参数给/bin目录下的busybox程序并调用它),mdev扫描 /sys/class 和 /sys/block 中所有的类设备目录,如果在目录中含有名为“dev”的文件,且文件中包含的是设备号,则mdev就利用这些信息为这个设备在/dev 下创建设备节点文件。一般只在启动时才执行一次 “mdev -s”。
热插拔事件:由于启动时运行了命 令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那么当有热插拔事件产生时,内核就会调用位于 /sbin目录的mdev。这时mdev通过环境变量中的 ACTION 和 DEVPATH,来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否“dev”的属性文件,如果有就利用这些信息为 这个设备在/dev 下创建设备节点文件
重新打包文件系统,这样/sys目录,/dev目录就有东西了
下面是create_class的原型:
#define class_create(owner, name) /
({ /
static struct lock_class_key __key; /
__class_create(owner, name, &__key); /
})
extern struct class * __must_check __class_create(struct mole *owner,
const char *name,
struct lock_class_key *key);
class_destroy的原型如下:
extern void class_destroy(struct class *cls);
device_create的原型如下:
extern struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
__attribute__((format(printf, 5, 6)));
device_destroy的原型如下:
extern void device_destroy(struct class *cls, dev_t devt);
具体使用如下,可参考后面的实例:
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
下面再来看一下应用程序如何找到这个结构体的
在应用程序中我们使用open打开一个设备:如:open(/dev/xxx, O_RDWR);
xxx有一个属性,如字符设备为c,后面为读写权限,还有主设备名、次设备名,我们注册时 通过register_chrdev(0, "first_drv", &first_drv_fops)(有主设备号,设备名,struct file_operations结构体)将first_drv_fops结构体注册到内核数组chrdev中去的,结构体中有open,write函数,那么应用程序如何找到它的,事实上是根据打开的这个文件的属性中的设备类型及主设备号在内核数组chrdev里面找到我们注册的first_drv_fops,
实例代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配置GPF4,5,6为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
_from_user(&val, buf, count); // _to_user();
if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_mole变量 */
.open = first_drv_open,
.write = first_drv_write,
};
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpfcon);
}
mole_init(first_drv_init);
mole_exit(first_drv_exit);
MODULE_LICENSE("GPL");
编译用Makefile文件
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` moles
clean:
make -C $(KERN_DIR) M=`pwd` moles clean
rm -rf moles.order
obj-m += first_drv.o
测试程序:
#include
#include
#include
#include
/* firstdrvtest on
* firstdrvtest off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xyz", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s \n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
H. linux显卡驱动怎么编译进内核
一、 驱动程序编译进内核的步骤
在 linux 内核中增加程序需要完成以下三项工作:
1. 将编写的源代码复制到 Linux 内核源代码的相应目录;
2. 在目录的 Kconfig 文件中增加新源代码对应项目的编译配置选项;
3. 在目录的 Makefile 文件中增加对新源代码的编译条目。
bq27501驱动编译到内核中具体步骤如下:
1. 先将驱动代码bq27501文件夹复制到 ti-davinci/drivers/ 目录下。
确定bq27501驱动模块应在内核源代码树中处于何处。
设备驱动程序存放在内核源码树根目录 drivers/ 的子目录下,在其内部,设备驱动文件进一步按照类别,类型等有序地组织起来。
a. 字符设备存在于 drivers/char/ 目录下
b. 块设备存放在 drivers/block/ 目录下
c. USB 设备则存放在 drivers/usb/ 目录下。
I. 我可以直接用gcc编译一个驱动程序吗
驱动程序肯定不能这么编译啦,驱动程序要用内核来编译的。Makefile也很复杂,我也看不太懂,但是Makefile里面要制定内核所在的目录以及生产的驱动文件名。这个Makefile可以直接去网上找吧,实在不行我传给你一个。把Makefile和驱动的
c文件
放在同一个目录下,make一下就可以产生.ko的驱动文件。移到
开发板
上insmod就可以测试了。但貌似要想在内核里配置上这个模块。
J. 修改了WINCE自带的驱动程序后如何编译
IDE 方式的编译很简单,以PB5.0为例,打开定制内核的工程,在左边的workspaceFileView中找到你已经修改了的目录,然后单击右键弹出菜单,在菜单中选择Build and Sysgen Current Project,这样PB就会编译指定的目录中的项目源码文件,然后执行sysgen命令根据source文件中的内容生成目标文件并复制到当前内核工程目录下。 命令行方式的编译需要打开Build OS Open Release Directory,以cd命令进入你已经修改的驱动程序目录中,然后键入build –cfs,然后键入 sysgen –p 项目名称,一般项目名称为source文件中的TARGETNAME。