进入驱动的HPI相关部分后,就要好好研究pci2040的datasheet了。说实话,一个驱动程序的大部分内容,就是把datasheet里的内容翻译成代码,所以每一个驱动工程师都应该深入的阅读硬件文档。
首先看pci2040的配置空间,如下所示
有两个最重要的基地址,一个是HPI CSR内存基地址,可以通过它把PCI2040的HPI CSR寄存器群映射到主机的内存;还有一个是控制空间基地址,可以把PCI2040的32KB的控制空间映射到主机内存。
HPI CSR寄存器如上所示,根据ioremap返回的基址加上相应的偏移,就可以顺序访问这些HPI CSR寄存器。可以看出这些寄存器都是用来控制DSP的工作状态的,比如中断的开关,重启DSP等等。那我们怎么把数据传递给DSP并获得DSP的运算结果?PCI2040的datasheet也提供了HPI DSP的资料,从中可以知道,与DSP的通信需要三个寄存器:地址寄存器HPIA,控制寄存器HPIC,数据寄存器HPID。这三个寄存器都可以通过映射PCI2040的控制空间来访问。映射成功后,每次与DSP的通信步骤如下:
(1)通过HPIC设置DSP访问模式
(2)通过HPIA设置访问的DSP内存地址
(3)通过HPID写入或者读取DSP的内存数据。
如果可以顺利访问DSP的内存了,那PCI2040驱动到这里就基本算完成了,可是用户态的应用程序怎么调用这个驱动呢?一般的设备驱动,都是需要先open,然后再ioctl来调用驱动的,不过这次我们的开发环境是rtai+linux,可以利用rtai的共享内存机制,来实现用户态程序与内核态驱动的通信。
在probe函数的最后,我们调用hpi_open()函数,该函数主要功能如下所示:
static int hpi_open (struct hpi_private_data *tp)
{
......
//开共享内存空间
g_shm=(lmcc_shm *)rtai_kmalloc(nam2num(LMCC_SHM_NAME), sizeof(lmcc_shm));
......
//设置任务模式
rt_set_oneshot_mode();
......
//初始化实时任务,相当于开一个内核线程
retval=rt_task_init(&g_task, UserRequestProc, 0, 8192, 3, 1, 0);
......
//启动rt定时器
period=start_rt_timer(0);
now=rt_get_time()+100*period;
//设置实时任务周期
rt_task_make_periodic(&g_task, now, nano2count(500000));// 0.5ms
......
}
用户态应用程序通过g_shm=(lmcc_shm *)rtai_malloc(nam2num(LMCC_SHM_NAME), sizeof(lmcc_shm))就可以得到这片共享内存的基地址,从而读写共享内存。实时任务每0.5ms查询一次共享内存,并对共享内存的改变做出反应。这样就实现了用户程序与驱动的通信。
至此,用户态的应用程序就可以读写DSP内存了,实现了驱动的基本功能,再根据用户需求,对读写操作做一层封装,方便用户使用,驱动就真正完成了。通过这个程序,可以对linux下的驱动到底是什么有个初步的认识,也可以对rtai下的实时linux有一些了解。