基于MPC8308的gpio管脚驱动测试linux2.6内核任务切换延时

时间:2022-04-15 17:54:52

1、目的:

为测试linux2.6.29.6内核的任务切换延时,考虑通过同时在后台运行的两个进程test_gpio0test_gpio1,对指定的gpio管脚分别拉低、拉高,从示波器观察波形变化的宽度来获得两个进程切换的延时。

2、准备

    1. MPC8308 MitxREV2开发板及附带BSP

    2. MPC8308 PowerQUICC IIPro Processor Reference Manual

    3. 示波器;

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.cgpio_test.hMakefile,其中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.cgpio_test.h

需要选择一个方便检测的GPIO管脚,在BSP/help/hardware/RDB下查看MPC8308-RDB_Schematics-2.pdf发现,所有GPIO023)只有GPIO9引出到J8插针4,其他的GPIO都没有引出来,选择GPIO9为检测管脚,从J8插针4测量。

基于MPC8308的gpio管脚驱动测试linux2.6内核任务切换延时

GPIO0GPIO23都是复用的,至于是否已被复用为其他引脚,例如TSEC,现在还不知道。尚未知GPIO9是否被u-boot或者kernel自带驱动(TSEC)复用为TSEC2_RX_ER,需要确定,确定的方法需要访问MPC8308的相关内部寄存器,查看文档MPC8308PowerQUICC II Pro Processor Reference Manual.pdf(简称mpc8308rm.pdf,此文档在BSP中并没有附带,需要去网上下载)

要访问内部寄存器,首先要知道immrmpc8308rm.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


immru-boot更改为0xE0000000,那么在驱动中也应采用此值。


static unsigned longimmr_base = 0xE0000000;


寄存器组sysconfSICRH寄存器(偏移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,最低值和最高值差别比较大,波形有很多重叠。