UIO這個東西是雖然是今年四月份的時候merge到kernel trunk,但其實在linux界很早就有相關的應用,因為它的技術很簡單,就是直接把kernel memory一對一映射到user space memory,藉由這樣mapping的方式,讓user program直接在user space存取hardware I/O
而UIO在kernel提供了一個簡單的架構,programmer只要設定好IO address和size,就可以使用UIO的功能,UIO會自動幫我們處理VMA相關的轉換動作,而UIO drvier的撰寫順序如下
1.宣告struct uio_info,並填入hardware資料
uio_info.name:drvier的名稱
uio_info.version:driver版本
uio_info.irq:中斷編號
uio_info.irq_flags:request_irq時需傳入的參數
uio_info.handler:中斷handler
uio_info.mem[]:hardware IO映射位址,可以有很多組
2.user space的程式很簡單,就是用open和mmap去存取hardware I/O
QT2410 LED UIO的範例我簡述如下,因為我driver註冊為platform driver,所以有改到arch/arm/mach-s3c2410/mach-smdk2410.c,請參照如下修改
static struct resource s3c_led_resources[] = {
[0] = {
.start = 0x56000000,
.end = 0x56000000 + 24,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_EINT23,
.end = IRQ_EINT23,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device s3c_led = {
.name = "JoeyLED",
.num_resources = ARRAY_SIZE(s3c_led_resources),
.resource = s3c_led_resources,
};
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&s3c_cs89x0,
&s3c_led,
};
新增led.c這個UIO driver,範例程式如下,重點在led_probe這個函式,可以看看該如何填寫uio_info這個structure的資料
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/uio_driver.h>
#include <linux/platform_device.h>
#define GPIOBAdr 0x56000014
#define DRV_NAME "JoeyLED"
#define DRV_VERSION "0.0.1"
static irqreturn_t led_handler(int irq, struct uio_info *dev_info)
{
printk("interrupt happened\n");
return IRQ_HANDLED;
}
static int __init led_probe(struct platform_device *dev)
{
struct resource *regs;
struct uio_info *info;
printk("probing led\n");
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
regs = platform_get_resource(dev, IORESOURCE_MEM, 0);
if (!regs)
{
dev_err(&dev->dev, "No memory resource specified\n");
return -ENODEV;
}else
{
printk("GPIOB reg->start %x reg->end %x \n",regs->start,regs->end );
}
info->mem[0].addr = regs->start;
info->mem[0].size=regs->end - regs->start;
info->mem[0].internal_addr=ioremap(regs->start, info->mem[0].size);;
if (!info->mem[0].addr)
{
dev_err(&dev->dev, "Invalid memory resource\n");
return -ENODEV;
}
info->mem[0].memtype = UIO_MEM_PHYS;
info->version = "0.0.1";
info->name="JoeyLED";
info->irq=platform_get_irq(dev, 0);
info->irq_flags = IRQF_SHARED;
info->handler = led_handler;
if (uio_register_device(&dev->dev, info))
{
iounmap(info->mem[0].internal_addr);
printk("uio_register failed\n");
return -ENODEV;
}
platform_set_drvdata(dev, info);
printk("probing led success\n");
return 0;
}
static int __exit led_remove(struct platform_device *dev)
{
struct uio_info *info = platform_get_drvdata(dev);
uio_unregister_device(info);
platform_set_drvdata(dev, NULL);
iounmap(info->mem[0].internal_addr);
kfree(info);
return 0;
}
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = __devexit_p(led_remove),
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
};
static int __init led_init(void)
{
printk("QT2410 LED driver demo\n");
return platform_driver_register(&led_driver);
}
static void __exit led_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_DESCRIPTION("led module");
MODULE_AUTHOR("Joey Cheng<jemicheng@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("QT2410:LED module");
Userspace程式的範例如下
#include <stdio.h>
#include <stdlib.h>
#include<sys/mman.h>
#include<fcntl.h>
#include <asm/page.h>
int main(void)
{
int mem_fd,i=0;
unsigned *led;
mem_fd=open("/dev/uio0",O_RDWR|O_SYNC);
if (mem_fd <0)
{
printf("open device error\n");
return;
}
led=mmap(NULL,24, PROT_READ | PROT_WRITE, MAP_SHARED,mem_fd, 0);
if (led == MAP_FAILED)
{
perror("mmap");
close(mem_fd);
exit(-1);
}
//led+=4;
led+=5;
while(i<3)
{
(*(volatile unsigned *)led)|=0x00000002;
printf("led mem %p content %d\n",led,*(unsigned *)led);
sleep(3);
(*(volatile unsigned *)led)&=~(0x00000002);
printf("led mem %p content %d\n",led,*(unsigned *)led);
i++;
sleep(3);
}
close(mem_fd);
}
記得要在kernel選單中選擇Userspace I/O drivers
重編kernel並試著跑跑看上面的範例來點亮板子上的led,修改過的檔案我簡列如下,記得在執行範例前先確定/dev/uio0是否有建立,請先cat /sys/class/uio/uio0/dev/的資訊,確定device node的major和minor(/sys這個資料夾是virtual sysfs mount出來的,請下指令mount -t sysfs sysfs /sys)
1.arch/arm/mach-s3c2410/mach-smdk2410.c
2.drivers/uio/led_kernel.c
3.drivers/uio/Makefile
4.drivers/uio/Kconfig
5.led.c(in user space)
UIO相關的參考資料請點這裡,這邊有個很棒的軟體叫lsuio,可以列出所有UIO的module和其映射的memory address,執行範例如下圖
留言