QNX系统启动后,执行一系列脚本命令,然后加载SPI驱动。
脚本SPI启动文件
spi-master -u3 -d Touch-espi base=0x02010000,irq=65
当QNX执行该脚本时,会自动到指定目录搜索是否存在spi-master驱动,然后后面一串是参数设置。这一串参数就像Linux设备树一样,指定相关的硬件参数。具体参数意义在驱动力在详细解释。
QNX执行spi-master后,应该执行函数入口在哪里?其实QNX早就为我们分配好了,它把所有的驱动当做应用程序,一个应用程序就像VC编写的应用程序一样,都有一个main入口。因此分析QNX SPI驱动,可以从这个函数开始。
int main(int argc, char *argv[])
{
spi_dev_t *head = NULL, *tail = NULL, *dev;
void *drventry, *dlhdl;
siginfo_t info;
sigset_t set;
int i, c, devnum = 0;
if (ThreadCtl(_NTO_TCTL_IO, 0) == -1) {
perror("ThreadCtl");
return (!EOK);
}
_spi_init_iofunc();//实例化io接口
while ((c = getopt(argc, argv, "u:d:")) != -1) {
switch (c) {
case 'u':
devnum = strtol(optarg, NULL, 0);
break;
case 'd':
if ((drventry = _spi_dlload(&dlhdl, optarg)) == NULL) {
perror("spi_load_driver() failed");
return (-1);
}
do {
if ((dev = calloc(1, sizeof(spi_dev_t))) == NULL)
goto cleanup;
if (argv[optind] == NULL || *argv[optind] == '-')
dev->opts = NULL;
else
dev->opts = strdup(argv[optind]);
++optind;
dev->funcs = (spi_funcs_t *)drventry;
dev->devnum = devnum++;
dev->dlhdl = dlhdl;
i = _spi_create_instance(dev);
if (i != EOK) {
perror("spi_create_instance() failed");
if (dev->opts)
free(dev->opts);
free(dev);
goto cleanup;
}
if (head) {
tail->next = dev;
tail = dev;
}
else
head = tail = dev;
} while (optind < argc && *(optarg = argv[optind]) != '-');
/*
* Now we only support one dll
*/
goto start_spi;
break;
}
}
start_spi:
if (head) {
/* background the process */
procmgr_daemon(0, PROCMGR_DAEMON_NOCLOSE | PROCMGR_DAEMON_NODEVNULL);
sigemptyset(&set);
sigaddset(&set, SIGTERM);
for (;;) {
if (SignalWaitinfo(&set, &info) == -1)
continue;
if (info.si_signo == SIGTERM)
break;
}
}
cleanup:
dev=head;
while (dev) {
if (dev->ctp) {
dispatch_unblock(dev->ctp);
}
if (dev->drvhdl) {
resmgr_detach(dev->dpp, dev->id, _RESMGR_DETACH_ALL);
dev->funcs->fini(dev->drvhdl);
}
if (dev->dpp) {
dispatch_destroy(dev->dpp);
}
head = dev->next;
if (dev->opts)
free(dev->opts);
free(dev);
dev=head;
}
dlclose(dlhdl);
return (EOK);
}
进入main后,执行ThreadCtl(_NTO_TCTL_IO, 0)函数,该函数使能超级锁定进程的内存和请求I/O特权;让线程在具有适当特权的架构上执行in、in、out、out、cli和sti I/O操作码,并让它附加IRQ处理程序。很多操作都需要进行寄存器操作,需要采用out32 in32接口等。
调用_spi_init_iofunc();初始化连接函数,通过 iofunc_func_init()函数初始化,通过连接和POSIX默认IO结构层功能。有关默认函数的信息。
int _spi_init_iofunc(void)
{
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &_spi_connect_funcs, _RESMGR_IO_NFUNCS, &_spi_io_funcs);
_spi_io_funcs.read = _spi_read;
_spi_io_funcs.write = _spi_write;
_spi_io_funcs.devctl = _spi_devctl;
_spi_io_funcs.close_ocb = _spi_close_ocb;
_spi_io_funcs.msg = _spi_iomsg;
return EOK;
}
完成后,开始解析参数命令,也就是前面提到的spi-master后的参数“-u3 -d Touch-espi base=0x02010000,irq=65”,其中u表示设备号,定义为spi3,-d表示加载的驱动名称链接库,调用 _spi_dlload(&dlhdl, optarg)函数,加载动态库,而寄存器基地址和中断号,不在这个参数里设置,后面在叙述。
void *_spi_dlload(void **hdl, const char *optarg)
{
char dllpath[_POSIX_PATH_MAX + 1];
void *dlhdl, *entry;
if (strchr(optarg, '/') != NULL)
strcpy(dllpath, optarg);
else
sprintf(dllpath, "spi-%s.so", optarg);
dlhdl = dlopen(dllpath, 0);
if (dlhdl != NULL) {
entry = dlsym(dlhdl, "spi_drv_entry");
if (entry != NULL) {
*hdl = dlhdl;
return entry;
}
dlclose(dlhdl);
}
return NULL;
}
_spi_dlload调用dlsym函数,找到动态链接库内的匹配函数名称spi_drv_entry。这个函数作为SPI底层驱动入口加载。在主函数参数命令里,调用 dev->funcs = (spi_funcs_t *)drventry;和 i = _spi_create_instance(dev);实例化驱动。最终完成系列的初始化过程,进入循环。
调用procmgr_daemon函数,置PROCMGR_DAEMON_NOCLOSE | PROCMGR_DAEMON_NODEVNULL标志,把该进程运行于后台。
sigemptyset(&set);//初始化一个不包含任何信号的集合
sigaddset(&set, SIGTERM);//设置SIGTERM信号。是一个程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和
处理. 通常用来要求程序自己正常退出. shell命令kill缺省产生这个信号。
SignalWaitinfo() 内核调用从set指定的集合中选择挂起信号。如果在调用时集合中没有挂起信号,线程将阻塞,直到集合中的一个或多个信号成为挂起信号,或者直到被未阻塞的捕获信号中断。在这里主要是捕获SIGTERM信号,当收到该信号,退出该驱动。 如果其中一项加载不成功,将会卸掉以前初始化的过程,其实就是初始化逆过程,相当于linux模块卸载函数。至此SPI主函数的过程已分析完成,接下了分析SPI 具体过程。
主要从_spi_create_instance(dev)函数开始,_spi_create_instance初始化线程参数,创建一个线程任务,这个线程函数为_spi_driver_thread。
int _spi_create_instance(spi_dev_t *dev)
{
pthread_attr_t pattr;
struct sched_param param;
if (NULL == (dev->dpp = dispatch_create())) {//动态分配一个句柄
perror("dispatch_create() failed");
goto failed0;
}
pthread_attr_init(&pattr);//初始化线程属性
pthread_attr_setschedpolicy(&pattr, SCHED_RR);//设置调度属性
param.sched_priority = 21;//设置优先级
pthread_attr_setschedparam(&pattr, ¶m);
pthread_attr_setinheritsched(&pattr, PTHREAD_EXPLICIT_SCHED);
// Create thread for this interface
if (pthread_create(NULL, &pattr, (void *)_spi_driver_thread, dev) != EOK) {//创建线程
perror("pthread_create() failed");
goto failed1;
}
_spi_driver_thread调用_spi_register_interface后进入主线程任务。其中_spi_register_interface在dev目录下创建一个设备节点和初始化SPI驱动,调用 dev->funcs->init(dev, dev->opts),这个函数指针在spi_drv_entry结构体中,在主函数里通过链接库完成指针赋值,spi_drv_entry里实现真正的SPI操作。包括初始化、配置、设备信息获取以及数据传输等等操作。
spi_funcs_t spi_drv_entry = {
sizeof(spi_funcs_t),
mx51_init, /* init() */
mx51_dinit, /* fini() */
mx51_drvinfo, /* drvinfo() */
mx51_devinfo, /* devinfo() */
mx51_setcfg, /* setcfg() */
mx51_xfer, /* xfer() */
NULL /* dma_xfer() */
};调用关系如下 dev->funcs->init(dev, dev->opts)--> mx51_init(void *hdl, char *options)-->mx51_options(mx51_cspi_t *dev, char *optstring)。
static int _spi_register_interface(void *data)
{
spi_dev_t *dev = data;
SPIDEV *drvhdl;
resmgr_attr_t rattr;
char devname[PATH_MAX + 1];
if ((drvhdl = dev->funcs->init(dev, dev->opts)) == NULL) {
free(dev->opts);
dev->opts = NULL;
return (!EOK);
}
dev->drvhdl = drvhdl;
/* set up i/o handler functions */
memset(&rattr, 0, sizeof(rattr));
rattr.nparts_max = SPI_RESMGR_NPARTS_MIN;
rattr.msg_max_size = SPI_RESMGR_MSGSIZE_MIN;
iofunc_attr_init(&drvhdl->attr, S_IFCHR | 0666, NULL, NULL);
drvhdl->attr.mount = &_spi_mount;
/* register device name */
snprintf(devname, PATH_MAX, "/dev/spi%d", dev->devnum);
if (-1 == (dev->id = resmgr_attach(dev->dpp, &rattr, devname, _FTYPE_ANY, 0,
&_spi_connect_funcs, &_spi_io_funcs, (void *)drvhdl))) {
perror("resmgr_attach() failed");
goto failed1;
}
resmgr_devino(dev->id, &drvhdl->attr.mount->dev, &drvhdl->attr.inode);
if ((dev->ctp = dispatch_context_alloc(dev->dpp)) != NULL)
return (EOK);
perror("dispatch_context_alloc() failed");
resmgr_detach(dev->dpp, dev->id, _RESMGR_DETACH_ALL);
failed1:
dev->funcs->fini(drvhdl);
return (!EOK);
}
在开始参数配置时,还有两个参数没有看到具体方法,其实在mx51_options实现,包括如下参数如base\irq\clock\csdelay等等。从而达到动态设置参数的目的,