花了半年时间在QNX系统上,这是一个RTOS,这个系统是高安全级别的系统,在核物理站/天文空间站/电站/地铁/交通运输(飞机/汽车/地铁)等工业系统领域占有70%以上的市场份额。
背景:本文将我个人在QNX上移植内核和开发驱动以及应用程序的部分经验记录在此,因公司商业机密,部分源码不便公开。我会框架性的讲解开发思路。为了简化文章复杂性,我只讨论相同板子的平台驱动转移,我手中是at91sam9260-ek的板子。外部设备是公司硬件部单独添加的。
目的:利用已有的Linux驱动,简化QNX的驱动编写。
大致思路:Linux的驱动是基于模块的,每个驱动作为内核的扩展放进内存中,QNX的驱动就是一个进程,需要将模块改为一个应用程序。调整内核与驱动的通信机制。
以RTC驱动为例子:
Linux的驱动开发框架为:
1.填充read/write/ioctl/probe等驱动回调函数,设置对应的接口。
staticstructfile_operationsrtc8025_fops={
.owner=THIS_MODULE,
.open=rtc_open,
.release=rtc_release,
.read=rtc_read,
.write=rtc_write,
.ioctl=rtc_ioctl,
};
2.编写每个部分的硬件相关代码。以write为例:
staticssize_trtc_write(structfile*filp,__userconstchar*buf,
size_tlen,loff_t*ppos){
charbuff[16];
VR_TIME*tm;
tm=kmalloc(sizeof(VR_TIME),GFP_KERNEL);
if(NULL==tm){
printk("Memoryerror!\n");
return-1;
}
if(copy_from_user(buff,buf,len))
return-EFAULT;
buff[len]='\0';
set_time_value(tm,buff);
set_sys_time(tm);
kfree(tm);
returnlen;
}
3.注册设备的initial接口函数与exit接口函数。
static__initintrtc8025_init(void){
intret;
printk("%sDriverVersion:%s\n",DRIVER_NAME,VERSION);
ret=register_chrdev(major,DRIVER_NAME,&rtc8025_fops);
if(ret
printk("unabletoregister%s\n",DRIVER_NAME);
returnret;
}
init_gpio();
msleep(10);
init_rtc();
return0;
}
static__exitvoidrtc8025_exit(void){
i2c_stime(0xe0,0x20);
i2c_stime(0xf0,0);
i2c_stime(0x70,0x00);
unregister_chrdev(major,DRIVER_NAME);
printk("%sunregister!\n",DRIVER_NAME);
}
module_init(rtc8025_init);
module_exit(rtc8025_exit);
其他具体实现就不在这里描述了。
原文参考自web开发网:http://www.software8.co/wzjs/
为了将原有的Linux驱动移植到QNX上,需要改动read/write以及ioctl的函数接口参数。并将与Linux内核相关的函数去掉(通常不会在QNX里面用到)以read函数代码为例:
size_trtc_read(char*buf,size_tlen,int*ppos){
VR_TIME*tm=(VR_TIME*)buf;
staticunsignedchartmp=0;
tmp=i2c_rtime(0x00)&0x7f;tm->second=BCD2DEC(tmp);
tmp=i2c_rtime(0x10)&0x7f;tm->minute=BCD2DEC(tmp);
tmp=i2c_rtime(0x20)&0x3f;tm->hour=BCD2DEC(tmp);
tmp=i2c_rtime(0x40)&0x3f;tm->day=BCD2DEC(tmp);
tmp=i2c_rtime(0x50)&0x1f;tm->month=BCD2DEC(tmp);
tmp=i2c_rtime(0x60);tm->year=BCD2DEC(tmp)+1920;
return0;
}
结构体VR_TIME是自定义的。
经过多次驱动的编写和调整,我发现还是Linux的那种回调机制在移植的时候代码改动最方便。因此,我自己也定义了一个结构体,用于模仿Linux的驱动框架,整合两个系统的差异。
structdriver_interface;
structdriver_interfaceget_driver_interface(){
structdriver_interfacedrv;
drv.drv_init=rtc_init;
drv.drv_read=rtc_read;
drv.drv_write=rtc_write;
drv.drv_ioctl=rtc_ioctl;
returndrv;
}
这个框架差异整合的结构体为:
structdriver_interface{
void(*drv_init)(void);
size_t(*drv_read)(char*buf,size_tlen,int*ppos);
size_t(*drv_write)(constchar*buf,size_tlen,int*ppos);
int(*drv_ioctl)(unsignedintcmd,unsignedlongarg);
};
structdriver_interfaceget_driver_interface();
然后我们来看看QNX的驱动框架,根据这个框架,我们将自己的drv整合进去,于是便有了下面的代码:
文件名为:driver_main.c
/*
* driver_main.c
*
*Thismodulecontainsthesourcecodeforthe/dev/rtc device.
*
*Thismodulecontainsallofthefunctionsnecessary.
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"linux2qnx_drv.h"
//#include"MMA8452Q.h"
#include"rtc.h"
#defineEXAMPLE_NAME"/dev/rtc"
structdriver_interfacedrv;
/*changetosomethingelseifsharingatarget*/
//#defineEXAMPLE_NAME"/dev/dagexample"
voidoptions(intargc,char*argv[]);
/*
*theseprototypesareneededsinceweareusingtheirnamesinmain()
*/
intio_open(resmgr_context_t*ctp,io_open_t*msg,RESMGR_HANDLE_T*handle,void*extra);
intio_read(resmgr_context_t*ctp,io_read_t*msg,RESMGR_OCB_T*ocb);
intio_write(resmgr_context_t*ctp,io_write_t*msg,RESMGR_OCB_T*ocb);
intio_devctl(resmgr_context_t*ctp,io_devctl_t*msg,RESMGR_OCB_T*ocb);
/*
*ourconnectandI/Ofunctions
*/
resmgr_connect_funcs_tconnect_funcs;
resmgr_io_funcs_tio_funcs;
/*
*ourdispatch,resourcemanagerandiofuncvariables
*/
dispatch_t*dpp;
resmgr_attr_trattr;
dispatch_context_t*ctp;
iofunc_attr_tioattr;
char*progname="rtc_driver";
intoptv;//-vforverboseoperation
intmain(intargc,char*argv[]){
printf("%s:starting
\n",progname);
intpathID;
options(argc,argv);
drv=get_driver_interface();
drv.drv_init();
/*
*allocateandinitializeadispatchstructureforusebyour
*mainloop
*/
dpp=dispatch_create();
if(dpp==NULL){
fprintf(stderr,"%s:couldn'tdispatch_create:%s\n",
progname,strerror(errno));
exit(1);
}
/*
*setuptheresourcemanagerattributesstructure,we'll
*usethisasawayofpassinginformationtoresmgr_attach().
*Fornow,wejustusedefaults.
*/
memset(&rattr,0,sizeof(rattr));/*usingthedefaultsforrattr*/
/*
*initializetheconnectfunctionsandI/Ofunctionstablesto
*theirdefaultsbycallingiofunc_func_init().
*
*connect_funcs,andio_funcsvariablesarealreadydeclared.
*
*/
iofunc_func_init(_RESMGR_CONNECT_NFUNCS,&connect_funcs,
_RESMGR_IO_NFUNCS,&io_funcs);
/*over-ridetheconnect_funcshandlerforopenwithourio_open,
*andover-ridetheio_funcshandlersforreadandwritewithour
*io_readandio_writehandlers
*/
connect_funcs.open=io_open;
io_funcs.read=io_read;
io_funcs.write=io_write;
io_funcs.devctl=io_devctl;
/*initializeourdevicedescriptionstructure
*/
iofunc_attr_init(&ioattr,S_IFCHR|0666,NULL,NULL);
/*
*callresmgr_attachtoregisterourprefixwiththe
*processmanager,andalsotoletitknowaboutourconnect
*andI/Ofunctions.
*
*Onerror,returns-1anderrnoisset.
*/
pathID=resmgr_attach(dpp,&rattr,EXAMPLE_NAME,_FTYPE_ANY,0,
&connect_funcs,&io_funcs,&ioattr);
if(pathID==-1){
fprintf(stderr,"%s:couldn'tattachpathname:%s\n",
progname,strerror(errno));
exit(1);
}
ctp=dispatch_context_alloc(dpp);
while(1){
if((ctp=dispatch_block(ctp))==NULL){
fprintf(stderr,"%s:dispatch_blockfailed:%s\n",
progname,strerror(errno));
exit(1);
}
dispatch_handler(ctp);
}
}
/*
*io_open
*
*wearecalledherewhentheclientdoesanopen.
*Itisuptoustoestablishacontext(inthis
*caseNULLwilldojustfine),andreturnastatus
*code.
*/
intio_open(resmgr_context_t*ctp,io_open_t*msg,
RESMGR_HANDLE_T*handle,void*extra){
if(optv){
printf("%s:inio_open\n",progname);
}
return(iofunc_open_default(ctp,msg,handle,extra));
}
/*
*io_read
*
*Atthispoint,theclienthascalledtheirlibrary"read"
*function,andexpectszeroormorebytes.Currentlyour
*/dev/exampleresourcemanagerreturnszerobytesto
*indicateEOF--nomorebytesexpected.
*
*Afterourexercises,itwillreturnsomedata.
*/
intio_read(resmgr_context_t*ctp,io_read_t*msg,RESMGR_OCB_T*ocb){
intstatus;
intnb=sizeof(VR_TIME);
staticVR_TIMEdata;
if(optv){
printf("%s:inio_read\n",progname);
}
if((status=iofunc_read_verify(ctp,msg,ocb,NULL))!=EOK){
if(optv)printf("readfailedbecauseoferror%d\n",status);
returnstatus;
}
//Nospecialxtypes
if((msg->i.xtype&_IO_XTYPE_MASK)!=_IO_XTYPE_NONE){
returnENOSYS;//causesMsgError(ctp->rcvid,ENOSYS);
}
drv.drv_read((char*)&data,sizeof(data),0);
nb=min(nb,msg->i.nbytes);
_IO_SET_READ_NBYTES(ctp,nb);//ctp->status=nb
SETIOV(ctp->iov,&data,nb);
if(nb>0)
ocb->attr->flags|=IOFUNC_ATTR_ATIME;
return_RESMGR_NPARTS(1);//causesMsgReplyv(ctp->rcvid,ctp->status,ctp->iov,1);
}
/*
*io_write
*
*Atthispoint,theclienthascalledtheirlibrary"write"
*function,andexpectsthatourresourcemanagerwillwrite
*thenumberofbytesthattheyhavespecifiedtosomedevice.
*
*Currently,for/dev/example,alloftheclientswritesalways
*work--theyjustgointoDeepOuterSpace.
*
*Afterourupdates,theywillbedisplayedonstandardout.
*/
intio_write(resmgr_context_t*ctp,io_write_t*msg,RESMGR_OCB_T*ocb){
intstatus;
intnb;
VR_TIME*buf;
if(optv){
printf("\n\n\n%s:inio_write,of%dbytes\n\n\n",progname,msg->i.nbytes);
}
if((status=iofunc_write_verify(ctp,msg,ocb,NULL))!=EOK)
returnstatus;
//Nospecialxtypes
if((msg->i.xtype&_IO_XTYPE_MASK)!=_IO_XTYPE_NONE){
returnENOSYS;
}
/*firstprocessanydataalreadyinthereceivebuff*/
//skiptheio_writeheadertogettothedata
buf=(VR_TIME*)(msg+1);
//calculatenumberofbytesofclientdatainreceivebuffer
nb=ctp->info.msglen-(ctp->offset+sizeof(*msg));
status=drv.drv_write((char*)buf,sizeof(*buf),0);
_IO_SET_WRITE_NBYTES(ctp,nb);
//ifweactuallyhandledanydata,markthatawritewasdonefor
//timeupdates(POSIXstuff)
if(nb>0)
ocb->attr->flags|=IOFUNC_ATTR_MTIME|IOFUNC_ATTR_CTIME;
return_RESMGR_NPARTS(0);
}
intio_devctl(resmgr_context_t*ctp,io_devctl_t*msg,RESMGR_OCB_T*ocb){
intnbytes,status;
union{/*Seenote1*/
XYZunion8arg_info_data;
intdata32;
/*
otherdevctltypesyoucanreceive*/
}*rx_data;
/*
LetcommoncodehandleDCMD_ALL_*cases.
Youcandothisbeforeorafteryouinterceptdevctls,depending
onyourintentions.Herewearen'tusinganypredefinedvalues,
soletthesystemonesbehandledfirst.Seenote2.
*/
if((status=iofunc_devctl_default(ctp,msg,ocb))!=
_RESMGR_DEFAULT){
return(status);
}
status=nbytes=0;
/*
Notethisassumesthatyoucanfittheentiredataportionof
thedevctlintoonemessage.Inrealityyoushouldprobably
performaMsgReadv()onceyouknowthetypeofmessageyou
havereceivedtogetallofthedata,ratherthanassume
itallfitsinthemessage.Wehavesetinourmainroutine
thatwe'llacceptatotalmessagesizeofupto2KB,sowe
don'tworryaboutitinthisexamplewherewedealwithints.
*/
/*Getthedatafromthemessage.SeeNote3.*/
rx_data=_DEVCTL_DATA(msg->i);
/*
Threeexamplesofdevctloperations:
SET:Setavalue(int)intheserver
GET:Getavalue(int)fromtheserver
SETGET:Setanewvalueandreturnthepreviousvalue
*/
_Int32tlinux_cmd=(msg->i.dcmd);
if((linux_cmd&SETVAL)&&((GETVAL&linux_cmd))){//SETGETisSETVAL|GETVAL==0x800000000|0x40000000
linux_cmd&=~SETGET;
linux_cmd-=(sizeof(XYZunion8)<<16);
//printf("SG=0x%0x",SETGET);
drv.drv_ioctl(linux_cmd,(unsignedlong)&(rx_data->arg_info_data));
nbytes=sizeof(rx_data->arg_info_data);
}elseif(linux_cmd&GETVAL){
linux_cmd&=~GETVAL;
linux_cmd-=(sizeof(int)<<16);
printf("G=0x%x",GETVAL);
printf(",linux_cmd=%d,dataaddress=%d\n",linux_cmd,(int)&(rx_data->data32));
drv.drv_ioctl(linux_cmd,(unsignedlong)&(rx_data->data32));
nbytes=sizeof(&rx_data->data32);
}elseif(linux_cmd&SETVAL){
linux_cmd&=~SETVAL;
linux_cmd-=(sizeof(int)<<16);
printf("S=0x%x",SETVAL);
printf(",linux_cmd=%d,data=%d\n",linux_cmd,rx_data->data32);
drv.drv_ioctl(linux_cmd,(int)(rx_data->data32));
}else{
printf("undefinedfunctionality\n");
//TODO
return-1;
}
/*Clearthereturnmessage.Notethatwesavedourdatapast
thislocationinthemessage.*/
memset(&msg->o,0,sizeof(msg->o));
/*
Ifyouwantedtopasssomethingdifferenttothereturn
fieldofthedevctl()youcoulddoitthroughthismember.
Seenote5.
*/
msg->o.ret_val=status;
/*Indicatethenumberofbytesandreturnthemessage*/
msg->o.nbytes=nbytes;
return(_RESMGR_PTR(ctp,&msg->o,sizeof(msg->o)+nbytes));
}
/*
*options
*
*Thisroutinehandlesthecommandlineoptions.
*Foroursimple/dev/example,wesupport:
*-vverboseoperation
*/
voidoptions(intargc,char*argv[]){
optv=0;
intopt;
while((opt=getopt(argc,argv,"v"))!=-1){
if(opt=='v'){
optv++;
}
}
}
这样,我们的Linux驱动就能基本原封不动的移植到QNX里面。
这是rtc.h:
#include
#include "MMA8452Q.h"
#ifndefRTC_COMMON_H_
#defineRTC_COMMON_H_
#defineGETVAL_POSIX_DEVDIR_FROM
#defineSETVAL_POSIX_DEVDIR_TO
#defineSETGET_POSIX_DEVDIR_TOFROM
typedefstructtagVRTIME{
intyear;
intmonth;
intday;
inthour;
intminute;
intsecond;
}VR_TIME;
#endif/*RTC_COMMON_H_*/
由于QNX使用了微内核架构,而驱动是一个进程,因此使用的进程间通信,对于ioctl要做特别处理,read和write可以直接使用,但是ioctl目前没有用到,在我的驱动中,后面加入了加速计传感器的驱动,需要ioctl进行扩展操作,但我最终使用QNX自己的devctl函数,看一下这个函数的使用代码。
#defineGETVAL_POSIX_DEVDIR_FROM
#defineSETVAL_POSIX_DEVDIR_TO
#defineSETGET_POSIX_DEVDIR_TOFROM
staticintinlineDEVCTL_CMD(intcode){
if((code&_POSIX_DEVDIR_TO)&&(code&_POSIX_DEVDIR_FROM))
return(sizeof(XYZunion8)<<16)+code;
if(code&_POSIX_DEVDIR_TO)
return(sizeof(int)<<16)+code;
if(code&_POSIX_DEVDIR_FROM)
return(sizeof(int)<<16)+code;
returncode;
}
staticvoidget_xyz(int*x,int*y,int*z){
XYZunion8_XYZdata8;
memset(&_XYZdata8,0,sizeof(_XYZdata8));
devctl(fd,DEVCTL_CMD(SETGET|CMD_READ_XYZ8),&_XYZdata8,sizeof(_XYZdata8),NULL);
*x=(int)_XYZdata8.Byte.Xdata8;
*y=(int)_XYZdata8.Byte.Ydata8;
*z=(int)_XYZdata8.Byte.Zdata8;
//printf("x=%4d,y=%4d,z=%4d\n",*x,*y,*z);
}
这样,就可以在QNX的应用程序中和驱动进行通信了。
最后附加上额外的两个源文件,叫做linux2qnx.c/linux2qnx.h用以整合两边对纯硬件的操作(地址映射与寄存器操作)的差异。
Linux2qnx.c:
/*
*linux2qnx_drv.c
*
*Createdon:Feb18,
*Author:mark
*/
#include
#include"linux2qnx_drv.h"
uintptr_tioremap(uint64_tio,size_tlen){
returnmmap_device_io(len,io);
}
voidiowrite16(uint16_tval,uintptr_tport){
out16(port,val);
}
uint_tioread16(uintptr_tport){
returnin16(port);
}
voidssleep(unsignedintseconds){
sleep(seconds);
}
voidiowrite32(uint32_tval,uintptr_tport){
out32(port,val);
}
uint_tioread32(uintptr_tport){
returnin32(port);
}
linux2qnx.h:
/*
*linux2qnx_drv.h
*
*Createdon:Feb18,
*Author:mark
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndefLINUX2QNX_DRV_H_
#defineLINUX2QNX_DRV_H_
#defineGETVAL_POSIX_DEVDIR_FROM
#defineSETVAL_POSIX_DEVDIR_TO
uintptr_tioremap(uint64_tio,size_tlen);
voidiowrite16(uint16_tval,uintptr_tport);
uint_tioread16(uintptr_tport);
uint_tioread32(uintptr_tport);
voidiowrite32(uint32_tval,uintptr_tport);
voidssleep(unsignedintseconds);
structdriver_interface{
void(*drv_init)(void);
size_t(*drv_read)(char*buf,size_tlen,int*ppos);
size_t(*drv_write)(constchar*buf,size_tlen,int*ppos);
int(*drv_ioctl)(unsignedintcmd,unsignedlongarg);
};
structdriver_interfaceget_driver_interface();
#endif/*LINUX2QNX_DRV_H_*/