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,執行範例如下圖

最後修改日期: 3 6 月, 2022

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。