1、目的:
为测试linux2.6.29.6内核的任务切换延时,考虑通过同时在后台运行的两个进程test_gpio0和test_gpio1,对指定的gpio管脚分别拉低、拉高,从示波器观察波形变化的宽度来获得两个进程切换的延时。
2、准备
MPC8308 MitxREV2开发板及附带BSP;
MPC8308 PowerQUICC IIPro Processor Reference Manual;
示波器;
3、内核树构建
在此采用开发板附带的ltib开发环境,在目录ltib-mpc8308erdb-20100413/下执行./ltib,构建过程完成后目录rpm/BUILD/linux即为构建完成的内核树目录,其中rpm/BUILD/linux/drivers/即为驱动所在目录。
4、驱动编写
转到目录rpm/BUILD/linux/drivers/下,修改Makefile,在最后一行后面添加:
obj-m += gpio_test/
在当前目录下,新建目录gpio_test/,新建文件gpio_test.c、gpio_test.h、Makefile,其中Makefile内容如下:
ifneq ($(KERNELRELEASE),)
obj-m := gpio_test.o
else
KDIR := ../.. #指向内核树
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD)
endif
clean:
rm -rf *.ko *.o *.mod.*modules.order
下面开始着手编写gpio_test.c和gpio_test.h。
需要选择一个方便检测的GPIO管脚,在BSP/help/hardware/RDB下查看MPC8308-RDB_Schematics-2.pdf发现,所有GPIO(0~23)只有GPIO9引出到J8插针4,其他的GPIO都没有引出来,选择GPIO9为检测管脚,从J8插针4测量。
GPIO0~GPIO23都是复用的,至于是否已被复用为其他引脚,例如TSEC,现在还不知道。尚未知GPIO9是否被u-boot或者kernel自带驱动(TSEC)复用为TSEC2_RX_ER,需要确定,确定的方法需要访问MPC8308的相关内部寄存器,查看文档MPC8308PowerQUICC II Pro Processor Reference Manual.pdf(简称mpc8308rm.pdf,此文档在BSP中并没有附带,需要去网上下载)。
要访问内部寄存器,首先要知道immr,mpc8308rm.pdf给出了immr的默认值为0xFF400000,这个值是可以被修改的,考虑到u-boot可能会修改此值,在ltib构建内核树过程中,也会有u-boot的构建,但在/rpm/BUILD下并没有发现u-boot的源文件,观察构建过程输出的信息发现,在u-boot构建完成后脚本就将/rpm/BUILD/u-boot-2009.11-rc1删除了,而要查看u-boot又必须去查看源文件,,需要执行:
./ltib -m prep -p u-boot
这样在/rpm/BUILD/下就会生成u-boot-2009.11-rc1目录,打开/u-boot-2009.11-rc1/include/configs/MPC8308ERDB.h,找到:
/*
* IMMR new address
*/
#defineCONFIG_SYS_IMMR 0xE0000000
即immr被u-boot更改为0xE0000000,那么在驱动中也应采用此值。
static unsigned longimmr_base = 0xE0000000;
寄存器组sysconf的SICRH寄存器(偏移0x00000114)给出了端口复用的相关选项,可通过读取此寄存器的值来查看端口复用情况。在2.4内核中,在内核空间读取内部寄存器的值只需根据immr加上偏移量得到物理地址直接读取即可,而在2.6内核中,直接读取物理地址会提示访问非法地址的出错,需要在初始化中采取ioremap方法将物理地址映射为内存虚拟地址才可操作,而且,在调用ioremap方法之前,还需要调用request_mem_region方法进行内存注册。
sysconf_phy_base =immr_base + sysconf_offset;
if (!request_mem_region( sysconf_phy_base, sysconf_len, "gpio_test")){
debugk(KERN_INFO"gpio_test: can't get I/O mem address 0x%lx\n",
gpio_phy_base);
return -ENODEV;
}
sysconf_virt_base =(ulong) ioremap( sysconf_phy_base, sysconf_len );
在驱动退出时,应释放相应空间。
iounmap((void __iomem*)sysconf_virt_base);
release_mem_region(sysconf_phy_base, sysconf_len);
据此读得SICRH= 0x41955003,对应GPIO_A=01,即GPIO9复用为TSEC,需要改写为GPIO_A=00。
*(volatileulong*)(sysconf_virt_base + sysconf_p_addr.sicrh) = 0x40955003
随后,就可以对GPIO进行编程了,在设置SICRH寄存器后,采用同样的request_mem_region方法和ioremap方法映射GPIO。
gpio_phy_base =immr_base + gpio_offset;
if (!request_mem_region( gpio_phy_base, gpio_len, "gpio_test")){
debugk(KERN_INFO"gpio_test: can't get I/O mem address 0x%lx\n",
gpio_phy_base);
return -ENODEV;
}
gpio_virt_base =(ulong) ioremap( gpio_phy_base, gpio_len );
退出时同样要注销。
iounmap((void __iomem*)gpio_virt_base);
release_mem_region(gpio_phy_base, gpio_len);
代码摘要如下。
io_p_addr_tio_p_addr[io_t_num] = { { 0x00000000, 0x00000004, 0x00000008,0x0000000C, 0x00000010, 0x00000014 }, };
sysconf_p_addr_tsysconf_p_addr = { 0x00000000, 0x00000004, 0x00000008, 0x0000000C,0x00000010, 0x00000014, 0x00000018 };
inline ulong io_pin_mask_32(int pin ) {
return (1 << (31 -pin));
}
void io_p_dir_set(mpc8308_io_t port, int pin, int data )
{
ulong pinmask;
pinmask = io_pin_mask_32(pin );
/**/
if( *((volatile ulong*)(gpio_virt_base + io_p_addr[port].odr)) != 0 )
*((volatile ulong*)(gpio_virt_base + io_p_addr[port].odr)) &= ~pinmask; //清除上拉模式
if( data )
{
*((volatile ulong*)(gpio_virt_base + io_p_addr[port].dir)) |= pinmask; // 1:output,0:input
}
else
{
*((volatile ulong*)(gpio_virt_base + io_p_addr[port].dir)) &= ~pinmask;
}
}
void io_p_dat_out(mpc8308_io_t port, int pin, int data )
{
ulong pinmask;
pinmask = io_pin_mask_32(pin );
if( data )
{
*((volatile ulong*)(gpio_virt_base + io_p_addr[port].dat)) |= pinmask; // 1:highlevel, 0:low level
}
else
{
*((volatile ulong*)(gpio_virt_base + io_p_addr[port].dat)) &= ~pinmask;
}
}
static int io_p_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsignedlong arg )
{
int minor =MINOR(inode->i_rdev);
int data;
ulong pinmask;
if( !gpio_virt_base )
return (-ENODEV);
pinmask = io_pin_mask_32(io_dev[minor].pin );
switch( cmd )
{
case STATUS_IOP_GET:
if( io_dev[minor].dir )
{
data =io_dev[minor].state;
}
else
{
io_dev[minor].state =io_p_dat_in( io_dev[minor].port, io_dev[minor].pin );
data =(io_dev[minor].state == io_dev[minor].mask) ? io_on : io_off;
}
return (copy_to_user((int *)arg, &data, sizeof(int) ));
break;
case STATUS_IOP_SET:
io_dev[minor].state =arg;
if( io_dev[minor].dir )
{
io_p_dat_out(io_dev[minor].port, io_dev[minor].pin, io_dev[minor].state ==io_dev[minor].mask );
}
return (0);
break;
};
return (-EINVAL);
}
5 测量任务切换延时
5.1 调整内核选项Timer Frequency,分别设置为100HZ,250HZ,1000HZ,测量波形从拉高到拉低的延时分别为10ms,4ms,1ms;
5.2 1000HZ情况下,调整内核选项:Ingo Molnar的实时补丁,它支持四种抢占模式:
(1)No Forced Preemption (Server),这种模式等同于没有使能抢占选项的标准内核,主要适用于科学计算等服务器环境。
测量延时结果为:1ms,波形清晰。
(2)Voluntary Kernel Preemption (Desktop),这种模式使能了自愿抢占,但仍然失效抢占内核选项,它通过增加抢占点缩减了抢占延迟,因此适用于一些需要较好的响应性的环境,如桌面环境,当然这种好的响应性是以牺牲一些吞吐率为代价的。
测量延时结果为:1ms,波形清晰。
(3)Preemptible Kernel (Low-Latency Desktop),这种模式既包含了自愿抢占,又使能了可抢占内核选项,因此有很好的响应延迟,实际上在一定程度上已经达到了软实时性。它主要适用于桌面和一些嵌入式系统,但是吞吐率比模式2更低。
测量延时结果为:平均1ms,最低值和最高值差别不大,波形有个别重叠。
(4)Complete Preemption (Real-Time),这种模式使能了所有实时功能,因此完全能够满足软实时需求,它适用于延迟要求为100微秒或稍低的实时系统。
测量延时结果为:平均1ms,最低值和最高值差别比较大,波形有很多重叠。