花了半年時(shí)間在QNX系統(tǒng)上,這是一個(gè)RTOS,這個(gè)系統(tǒng)是高安全級(jí)別的系統(tǒng),在核物理站/天文空間站/電站/地鐵/交通運(yùn)輸(飛機(jī)/汽車/地鐵)等工業(yè)系統(tǒng)領(lǐng)域占有70%以上的市場(chǎng)份額。背景:本文將我個(gè)人在QNX上移植內(nèi)核和開(kāi)發(fā)驅(qū)動(dòng)以及應(yīng)用程序的部分經(jīng)驗(yàn)記錄在此,因公司商業(yè)機(jī)密,部分源碼不便公開(kāi)。我會(huì)框架性的講解開(kāi)發(fā)思路。為了簡(jiǎn)化文章復(fù)雜性,我只討論相同板子的平臺(tái)驅(qū)動(dòng)轉(zhuǎn)移,我手中是at91sam9260-ek的板子。外部設(shè)備是公司硬件部單獨(dú)添加的。
目的:利用已有的Linux驅(qū)動(dòng),簡(jiǎn)化QNX的驅(qū)動(dòng)編寫(xiě)。
大致思路:Linux的驅(qū)動(dòng)是基于模塊的,每個(gè)驅(qū)動(dòng)作為內(nèi)核的擴(kuò)展放進(jìn)內(nèi)存中,QNX的驅(qū)動(dòng)就是一個(gè)進(jìn)程,需要將模塊改為一個(gè)應(yīng)用程序。調(diào)整內(nèi)核與驅(qū)動(dòng)的通信機(jī)制。
以RTC驅(qū)動(dòng)為例子:Linux的驅(qū)動(dòng)開(kāi)發(fā)框架為:
1.填充read/write/ioctl/probe等驅(qū)動(dòng)回調(diào)函數(shù),設(shè)置對(duì)應(yīng)的接口。
static struct file_operations rtc8025_fops = {
.owner = THIS_MODULE,
.open = rtc_open,
.release = rtc_release,
.read = rtc_read,
.write = rtc_write,
.ioctl = rtc_ioctl,
};
2.編寫(xiě)每個(gè)部分的硬件相關(guān)代碼。以write為例:
static ssize_t rtc_write(struct file *filp, __user const char *buf,
size_t len,loff_t *ppos){
char buff[16];
VR_TIME *tm;
tm = kmalloc(sizeof(VR_TIME),GFP_KERNEL);
if ( NULL == tm ){
printk("Memory error!\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);
return len;
}
3.注冊(cè)設(shè)備的initial接口函數(shù)與exit接口函數(shù)。
static __init int rtc8025_init(void){
int ret;
printk("%s Driver Version: %s\n", DRIVER_NAME, VERSION);
ret = register_chrdev(major,DRIVER_NAME,&rtc8025_fops);
if ( ret < 0 ){
printk("unable to register %s\n",DRIVER_NAME);
return ret;
}
init_gpio();
msleep(10);
init_rtc();
return 0;
}
static __exit void rtc8025_exit(void){
i2c_stime(0xe0, 0x20);
i2c_stime(0xf0, 0);
i2c_stime(0x70, 0x00);
unregister_chrdev(major, DRIVER_NAME);
printk("%s unregister!\n", DRIVER_NAME);
}
module_init(rtc8025_init);
module_exit(rtc8025_exit);
其他具體實(shí)現(xiàn)就不在這里描述了。
為了將原有的Linux驅(qū)動(dòng)移植到QNX上,需要改動(dòng)read/write以及ioctl的函數(shù)接口參數(shù)。并將與Linux內(nèi)核相關(guān)的函數(shù)去掉(通常不會(huì)在QNX里面用到)以read函數(shù)代碼為例:
size_t rtc_read(char *buf, size_t len, int *ppos){
VR_TIME *tm = (VR_TIME *)buf;
static unsigned char tmp = 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;
return 0;
}
結(jié)構(gòu)體VR_TIME是自定義的。
經(jīng)過(guò)多次驅(qū)動(dòng)的編寫(xiě)和調(diào)整,我發(fā)現(xiàn)還是Linux的那種回調(diào)機(jī)制在移植的時(shí)候代碼改動(dòng)最方便。因此,我自己也定義了一個(gè)結(jié)構(gòu)體,用于模仿Linux的驅(qū)動(dòng)框架,整合兩個(gè)系統(tǒng)的差異。
struct driver_interface ;
struct driver_interface get_driver_interface(){
struct driver_interface drv;
drv.drv_init = rtc_init;
drv.drv_read = rtc_read;
drv.drv_write = rtc_write;
drv.drv_ioctl = rtc_ioctl;
return drv;
}
這個(gè)框架差異整合的結(jié)構(gòu)體為:
struct driver_interface{
void (*drv_init)(void);
size_t (*drv_read)(char *buf, size_t len, int *ppos);
size_t (*drv_write)( const char *buf, size_t len, int *ppos);
int (*drv_ioctl)(unsigned int cmd, unsigned long arg);
};
struct driver_interface get_driver_interface();
然后我們來(lái)看看QNX的驅(qū)動(dòng)框架,根據(jù)這個(gè)框架,我們將自己的drv整合進(jìn)去,于是便有了下面的代碼:
文件名為:driver_main.c
/*
* driver_main.c
*
* This module contains the source code for the /dev/rtc device.
*
* This module contains all of the functions necessary.
*
*/#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <
string.h>
#include <unistd.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <sys/neutrino.h>
#include <sys/resmgr.h>
#include <arm/atmel-at91sam9260.h>
#include "linux2qnx_drv.h"
//#include "MMA8452Q.h"
#include "rtc.h"
#define EXAMPLE_NAME "/dev/rtc"
struct driver_interface drv;
/* change to something else if sharing a target */// #define EXAMPLE_NAME "/dev/dagexample"
void options (
int argc,
char *argv[]);
/*
* these prototypes are needed since we are using their names in main ()
*/int io_open (resmgr_context_t *ctp, io_open_t *msg, RESMGR_HANDLE_T *handle,
void *extra);
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);
int io_write(resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb);
int io_devctl(resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb);
/*
* our connect and I/O functions
*/resmgr_connect_funcs_t connect_funcs;
resmgr_io_funcs_t io_funcs;
/*
* our dispatch, resource manager and iofunc variables
*/dispatch_t *dpp;
resmgr_attr_t rattr;
dispatch_context_t *ctp;
iofunc_attr_t ioattr;
char *progname = "rtc_driver";
int optv;
// -v for verbose operation
int main (
int argc,
char *argv[]){
printf ("%s: starting

\n", progname);
int pathID;
options (argc, argv);
drv = get_driver_interface();
drv.drv_init();
/*
* allocate and initialize a dispatch structure for use by our
* main loop
*/ dpp = dispatch_create ();
if (dpp == NULL) {
fprintf (stderr, "%s: couldn't dispatch_create: %s\n",
progname, strerror (errno));
exit (1);
}
/*
* set up the resource manager attributes structure, we'll
* use this as a way of passing information to resmgr_attach().
* For now, we just use defaults.
*/ memset (&rattr, 0,
sizeof (rattr));
/* using the defaults for rattr */ /*
* initialize the connect functions and I/O functions tables to
* their defaults by calling iofunc_func_init().
*
* connect_funcs, and io_funcs variables are already declared.
*
*/ iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
/* over-ride the connect_funcs handler for open with our io_open,
* and over-ride the io_funcs handlers for read and write with our
* io_read and io_write handlers
*/ connect_funcs.open = io_open;
io_funcs.read = io_read;
io_funcs.write = io_write;
io_funcs.devctl = io_devctl;
/* initialize our device description structure
*/ iofunc_attr_init (&ioattr, S_IFCHR | 0666, NULL, NULL);
/*
* call resmgr_attach to register our prefix with the
* process manager, and also to let it know about our connect
* and I/O functions.
*
* On error, returns -1 and errno is set.
*/ pathID = resmgr_attach (dpp, &rattr, EXAMPLE_NAME, _FTYPE_ANY, 0,
&connect_funcs, &io_funcs, &ioattr);
if (pathID == -1) {
fprintf (stderr, "%s: couldn't attach pathname: %s\n",
progname, strerror (errno));
exit (1);
}
ctp = dispatch_context_alloc (dpp);
while (1) {
if ((ctp = dispatch_block (ctp)) == NULL) {
fprintf (stderr, "%s: dispatch_block failed: %s\n",
progname, strerror (errno));
exit (1);
}
dispatch_handler (ctp);
}
}
/*
* io_open
*
* we are called here when the client does an open.
* It is up to us to establish a context (in this
* case NULL will do just fine), and return a status
* code.
*/int io_open (resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle,
void *extra){
if (optv) {
printf ("%s: in io_open\n", progname);
}
return (iofunc_open_default (ctp, msg, handle, extra));
}
/*
* io_read
*
* At this point, the client has called their library "read"
* function, and expects zero or more bytes. Currently our
* /dev/example resource manager returns zero bytes to
* indicate EOF -- no more bytes expected.
*
* After our exercises, it will return some data.
*/int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb){
int status;
int nb =
sizeof(VR_TIME);
static VR_TIME data;
if (optv) {
printf ("%s: in io_read\n", progname);
}
if ((status = iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK) {
if (optv) printf("read failed because of error %d\n", status );
return status;
}
// No special xtypes
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) {
return ENOSYS;
// causes MsgError( 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);
// causes MsgReplyv( ctp->rcvid, ctp->status, ctp->iov, 1 );
}
/*
* io_write
*
* At this point, the client has called their library "write"
* function, and expects that our resource manager will write
* the number of bytes that they have specified to some device.
*
* Currently, for /dev/example, all of the clients writes always
* work -- they just go into Deep Outer Space.
*
* After our updates, they will be displayed on standard out.
*/int io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb){
int status;
int nb;
VR_TIME *buf;
if (optv) {
printf ("\n\n\n%s: in io_write, of %d bytes\n\n\n", progname, msg->i.nbytes);
}
if ((status = iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK)
return status;
// No special xtypes
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) {
return ENOSYS;
}
/* first process any data already in the receive buff */ // skip the io_write header to get to the data
buf = (VR_TIME *)(msg+1);
// calculate number of bytes of client data in receive buffer
nb = ctp->info.msglen - (ctp->offset +
sizeof(*msg) );
status = drv.drv_write( (
char*)buf,
sizeof(*buf), 0 );
_IO_SET_WRITE_NBYTES (ctp, nb);
// if we actually handled any data, mark that a write was done for
// time updates (POSIX stuff)
if (nb > 0)
ocb->attr->flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME;
return _RESMGR_NPARTS (0);
}
int io_devctl(resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb){
int nbytes, status;
union {
/* See note 1 */ XYZunion8 arg_info_data;
int data32;
/*
other devctl types you can receive */ } *rx_data;
/*
Let common code handle DCMD_ALL_* cases.
You can do this before or after you intercept devctls, depending
on your intentions. Here we aren't using any predefined values,
so let the system ones be handled first. See note 2.
*/ if ((status = iofunc_devctl_default(ctp, msg, ocb)) !=
_RESMGR_DEFAULT) {
return(status);
}
status = nbytes = 0;
/*
Note this assumes that you can fit the entire data portion of
the devctl into one message. In reality you should probably
perform a MsgReadv() once you know the type of message you
have received to get all of the data, rather than assume
it all fits in the message. We have set in our main routine
that we'll accept a total message size of up to 2 KB, so we
don't worry about it in this example where we deal with ints.
*/ /* Get the data from the message. See Note 3. */ rx_data = _DEVCTL_DATA(msg->i);
/*
Three examples of devctl operations:
SET: Set a value (int) in the server
GET: Get a value (int) from the server
SETGET: Set a new value and return the previous value
*/ _Int32t linux_cmd = (msg->i.dcmd);
if((linux_cmd & SETVAL) && ((GETVAL & linux_cmd))){
// SETGET is SETVAL|GETVAL == 0x800000000|0x40000000
linux_cmd &= ~SETGET;
linux_cmd -= (
sizeof(XYZunion8)<<16);
//printf("SG = 0x%0x", SETGET);
drv.drv_ioctl(linux_cmd, (unsigned
long)&(rx_data->arg_info_data) );
nbytes =
sizeof( rx_data->arg_info_data );
}
else if(linux_cmd & GETVAL){
linux_cmd &= ~GETVAL;
linux_cmd -= (
sizeof(
int)<<16);
printf("G = 0x%x", GETVAL);
printf(", linux_cmd=%d, data address =%d\n", linux_cmd, (
int)&(rx_data->data32));
drv.drv_ioctl(linux_cmd, (unsigned
long )&(rx_data->data32));
nbytes =
sizeof(&rx_data->data32);
}
else if(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("undefined functionality\n");
//TODO
return -1;
}
/* Clear the return message. Note that we saved our data past
this location in the message. */ memset(&msg->o, 0,
sizeof(msg->o));
/*
If you wanted to pass something different to the return
field of the devctl() you could do it through this member.
See note 5.
*/ msg->o.ret_val = status;
/* Indicate the number of bytes and return the message */ msg->o.nbytes = nbytes;
return(_RESMGR_PTR(ctp, &msg->o,
sizeof(msg->o) + nbytes));
}
/*
* options
*
* This routine handles the command line options.
* For our simple /dev/example, we support:
* -v verbose operation
*/void options (
int argc,
char *argv[]){
optv = 0;
int opt;
while ((opt = getopt (argc, argv, "v")) != -1) {
if( opt == 'v' ){
optv++;
}
}
}
這樣,我們的Linux驅(qū)動(dòng)就能基本原封不動(dòng)的移植到QNX里面。
這是rtc.h:
#include <devctl.h>
#include "MMA8452Q.h"
#ifndef RTC_COMMON_H_#define RTC_COMMON_H_#define GETVAL _POSIX_DEVDIR_FROM#define SETVAL _POSIX_DEVDIR_TO#define SETGET _POSIX_DEVDIR_TOFROMtypedef struct tagVRTIME{ int year; int month; int day; int hour; int minute; int second;}VR_TIME;#endif /* RTC_COMMON_H_ */由于QNX使用了微內(nèi)核架構(gòu),而驅(qū)動(dòng)是一個(gè)進(jìn)程,因此使用的進(jìn)程間通信,對(duì)于ioctl要做特別處理,read和write可以直接使用,但是ioctl目前沒(méi)有用到,在我的驅(qū)動(dòng)中,后面加入了加速計(jì)傳感器的驅(qū)動(dòng),需要ioctl進(jìn)行擴(kuò)展操作,但我最終使用QNX自己的devctl函數(shù),看一下這個(gè)函數(shù)的使用代碼。
#define GETVAL _POSIX_DEVDIR_FROM
#define SETVAL _POSIX_DEVDIR_TO
#define SETGET _POSIX_DEVDIR_TOFROM
static int inline DEVCTL_CMD( int code){
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;
return code;
}
static void get_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的應(yīng)用程序中和驅(qū)動(dòng)進(jìn)行通信了。
最后附加上額外的兩個(gè)源文件,叫做linux2qnx.c/linux2qnx.h用以整合兩邊對(duì)純硬件的操作(地址映射與寄存器操作)的差異。
Linux2qnx.c:
/*
* linux2qnx_drv.c
*
* Created on: Feb 18, 2013
* Author: mark
*/
#include <arm/inout.h>
#include "linux2qnx_drv.h"
uintptr_t ioremap(uint64_t io, size_t len){
return mmap_device_io(len, io);
}
void iowrite16( uint16_t val, uintptr_t port ){
out16(port, val);
}
uint_t ioread16(uintptr_t port){
return in16(port);
}
void ssleep(unsigned int seconds){
sleep(seconds);
}
void iowrite32(uint32_t val, uintptr_t port){
out32(port, val);
}
uint_t ioread32(uintptr_t port){
return in32(port);
}
linux2qnx.h:
/*
* linux2qnx_drv.h
*
* Created on: Feb 18, 2013
* Author: mark
*/
#include <stddef.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ioctl.h>
#include <fcntl.h>
#include <devctl.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <sys/neutrino.h>
#include <sys/resmgr.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arm/atmel-at91sam9260.h>
#ifndef LINUX2QNX_DRV_H_
#define LINUX2QNX_DRV_H_
#define GETVAL _POSIX_DEVDIR_FROM
#define SETVAL _POSIX_DEVDIR_TO
uintptr_t ioremap(uint64_t io, size_t len);
void iowrite16( uint16_t val, uintptr_t port );
uint_t ioread16(uintptr_t port);
uint_t ioread32(uintptr_t port);
void iowrite32(uint32_t val, uintptr_t port);
void ssleep(unsigned int seconds);
struct driver_interface{
void (*drv_init)(void);
size_t (*drv_read)(char *buf, size_t len, int *ppos);
size_t (*drv_write)( const char *buf, size_t len, int *ppos);
int (*drv_ioctl)(unsigned int cmd, unsigned long arg);
};
struct driver_interface get_driver_interface();
#endif /* LINUX2QNX_DRV_H_ */