http://lwn.net/Articles/315832/ /* uio_helper.c UIO helper functions. Copyright (C) 2007 Hans J. Koch This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <dirent.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include "uio_helper.h" int uio_get_mem_size(struct uio_info_t* info, int map_num) { int ret; char filename[64]; info->maps[map_num].size = UIO_INVALID_SIZE; sprintf(filename, "/sys/class/uio/uio%d/maps/map%d/size", info->uio_num, map_num); FILE* file = fopen(filename,"r"); if (!file) return -1; ret = fscanf(file,"0x%x",&info->maps[map_num].size); fclose(file); if (ret<0) return -2; return 0; } int uio_get_mem_addr(struct uio_info_t* info, int map_num) { int ret; char filename[64]; info->maps[map_num].addr = UIO_INVALID_ADDR; sprintf(filename, "/sys/class/uio/uio%d/maps/map%d/addr", info->uio_num, map_num); FILE* file = fopen(filename,"r"); if (!file) return -1; ret = fscanf(file,"0x%lx",&info->maps[map_num].addr); fclose(file); if (ret<0) return -2; return 0; } int uio_get_port_size(struct uio_info_t* info, int port_num) { int ret; char filename[64]; info->ports[port_num].size = UIO_INVALID_SIZE; sprintf(filename, "/sys/class/uio/uio%d/portio/port%d/size", info->uio_num, port_num); FILE* file = fopen(filename,"r"); if (!file) return -1; ret = fscanf(file,"0x%x",&info->ports[port_num].size); fclose(file); if (ret<0) return -2; return 0; } int uio_get_port_start(struct uio_info_t* info, int port_num) { int ret; char filename[64]; info->ports[port_num].start = UIO_INVALID_ADDR; sprintf(filename, "/sys/class/uio/uio%d/portio/port%d/start", info->uio_num, port_num); FILE* file = fopen(filename,"r"); if (!file) return -1; ret = fscanf(file,"0x%lx",&info->ports[port_num].start); fclose(file); if (ret<0) return -2; return 0; } int uio_get_event_count(struct uio_info_t* info) { int ret; char filename[64]; info->event_count = 0; sprintf(filename, "/sys/class/uio/uio%d/event", info->uio_num); FILE* file = fopen(filename,"r"); if (!file) return -1; ret = fscanf(file,"%lu",&info->event_count); fclose(file); if (ret<0) return -2; return 0; } int line_from_file(char *filename, char *linebuf) { char *s; int i; memset(linebuf, 0, UIO_MAX_NAME_SIZE); FILE* file = fopen(filename,"r"); if (!file) return -1; s = fgets(linebuf,UIO_MAX_NAME_SIZE,file); if (!s) return -2; for (i=0; (*s)&&(i<UIO_MAX_NAME_SIZE); i++) { if (*s == '\n') *s = 0; s++; } return 0; } int uio_get_name(struct uio_info_t* info) { char filename[64]; sprintf(filename, "/sys/class/uio/uio%d/name", info->uio_num); return line_from_file(filename, info->name); } int uio_get_version(struct uio_info_t* info) { char filename[64]; sprintf(filename, "/sys/class/uio/uio%d/version", info->uio_num); return line_from_file(filename, info->version); } int uio_get_all_info(struct uio_info_t* info) { int i; if (!info) return -1; if ((info->uio_num < 0)||(info->uio_num > UIO_MAX_NUM)) return -1; for (i = 0; i < MAX_UIO_MAPS; i++) { uio_get_mem_size(info, i); uio_get_mem_addr(info, i); uio_get_port_start(info, i); uio_get_port_size(info, i); } uio_get_event_count(info); uio_get_name(info); uio_get_version(info); return 0; } int dev_attr_filter(char *filename) { struct stat filestat; if (lstat(filename, &filestat)) return 0; if (S_ISREG(filestat.st_mode)) return 1; return 0; } int uio_get_device_attributes(struct uio_info_t* info) { struct dirent **namelist; struct uio_dev_attr_t *attr, *last = NULL; char fullname[96]; int n; info->dev_attrs = NULL; sprintf(fullname, "/sys/class/uio/uio%d/device", info->uio_num); n = scandir(fullname, &namelist, 0, alphasort); if (n < 0) return -1; while(n--) { sprintf(fullname, "/sys/class/uio/uio%d/device/%s", info->uio_num, namelist[n]->d_name); if (!dev_attr_filter(fullname)) continue; attr = malloc(sizeof(struct uio_dev_attr_t)); if (!attr) return -1; strncpy(attr->name, namelist[n]->d_name, UIO_MAX_NAME_SIZE); free(namelist[n]); if (line_from_file(fullname, attr->value)) { free(attr); continue; } if (!info->dev_attrs) info->dev_attrs = attr; else last->next = attr; attr->next = NULL; last = attr; } free(namelist); return 0; } void uio_free_dev_attrs(struct uio_info_t* info) { struct uio_dev_attr_t *p1, *p2; p1 = info->dev_attrs; while (p1) { p2 = p1->next; free(p1); p1 = p2; } info->dev_attrs = NULL; } void uio_free_info(struct uio_info_t* info) { struct uio_info_t *p1,*p2; p1 = info; while (p1) { uio_free_dev_attrs(p1); p2 = p1->next; free(p1); p1 = p2; } } int uio_num_from_filename(char* name) { enum scan_states { ss_u, ss_i, ss_o, ss_num, ss_err }; enum scan_states state = ss_u; int i=0, num = -1; char ch = name[0]; while (ch && (state != ss_err)) { switch (ch) { case 'u': if (state == ss_u) state = ss_i; else state = ss_err; break; case 'i': if (state == ss_i) state = ss_o; else state = ss_err; break; case 'o': if (state == ss_o) state = ss_num; else state = ss_err; break; default: if ( (ch>='0') && (ch<='9') &&(state == ss_num) ) { if (num < 0) num = (ch - '0'); else num = (num * 10) + (ch - '0'); } else state = ss_err; } i++; ch = name[i]; } if (state == ss_err) num = -1; return num; } static struct uio_info_t* info_from_name(char* name, int filter_num) { struct uio_info_t* info; int num = uio_num_from_filename(name); if (num < 0) return NULL; if ((filter_num >= 0) && (num != filter_num)) return NULL; info = malloc(sizeof(struct uio_info_t)); if (!info) return NULL; memset(info,0,sizeof(struct uio_info_t)); info->uio_num = num; return info; } struct uio_info_t* uio_find_devices(int filter_num) { struct dirent **namelist; struct uio_info_t *infolist = NULL, *infp, *last = NULL; int n; n = scandir("/sys/class/uio", &namelist, 0, alphasort); if (n < 0) return NULL; while(n--) { infp = info_from_name(namelist[n]->d_name, filter_num); free(namelist[n]); if (!infp) continue; if (!infolist) infolist = infp; else last->next = infp; last = infp; } free(namelist); return infolist; } void uio_single_mmap_test(struct uio_info_t* info, int map_num) { info->maps[map_num].mmap_result = UIO_MMAP_NOT_DONE; if (info->maps[map_num].size <= 0) return; info->maps[map_num].mmap_result = UIO_MMAP_FAILED; char dev_name[16]; sprintf(dev_name,"/dev/uio%d",info->uio_num); int fd = open(dev_name,O_RDONLY); if (fd < 0) return; void* map_addr = mmap( NULL, info->maps[map_num].size, PROT_READ, MAP_SHARED, fd, map_num*getpagesize()); if (map_addr != MAP_FAILED) { info->maps[map_num].mmap_result = UIO_MMAP_OK; munmap(map_addr, info->maps[map_num].size); } close(fd); } void uio_mmap_test(struct uio_info_t* info) { int map_num; for (map_num= 0; map_num < MAX_UIO_MAPS; map_num++) uio_single_mmap_test(info, map_num); } /* uioport.c - UIO I/O port utility usage: ./uioport uiostring inb addr ./uioport uiostring outb addr val git clone git://ifup.org/philips/uioport.git Copyright (C) 2009 Brandon D. Philips <bran...@ifup.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <grp.h> #include <pwd.h> #include <stdlib.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <sys/capability.h> #include <sys/io.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include "uio_helper.h" #define NUMGROUPS 255 int uio_filter = -1; /* * uid_has_write_perms - ensure the executing user has write permission to the * /dev/uid* node */ int uid_has_write_perms(char *path) { gid_t *groups; struct passwd *pw; cap_t cap; cap_flag_value_t cap_val; uid_t uid; struct stat st; int ngroups = NUMGROUPS; int ret = -EPERM; int j; cap = cap_get_proc(); cap_get_flag(cap, CAP_SYS_RAWIO, CAP_PERMITTED, &cap_val); if (cap_val != CAP_SET) { printf("must be ran with CAP_SYS_RAWIO\n"); return -EPERM; } uid = getuid(); pw = getpwuid(uid); if (pw == NULL) { perror("getpwnam"); exit(EXIT_FAILURE); } groups = malloc(ngroups * sizeof (gid_t)); if (groups == NULL) { perror("malloc"); exit(EXIT_FAILURE); } if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups) == -1) { fprintf(stderr, "getgrouplist() returned -1"); exit(EXIT_FAILURE); } if (stat(path, &st) < 0) { perror("stat"); exit(EXIT_FAILURE); } /* If user has write permissions to path and matches the executing user * then allow io port operations */ if (st.st_mode & S_IWUSR) { if (uid == st.st_uid) { ret = 0; goto out_free; } } /* If group has write permissions to path and user belongs to that * group allow io port operations */ if (st.st_mode & S_IWGRP) { for (j = 0; j < ngroups; j++) { if (groups[j] == st.st_gid) { ret = 0; goto out_free; } } } if (st.st_mode & S_IWOTH) { ret = 0; goto out_free; } out_free: free(groups); cap_free(cap); return ret; } int addr_in_range(struct uio_info_t *p, int op_addr, int op_size) { int base = p->ports[0].start; int size = p->ports[0].size; if ((op_addr > (base + size)) || ((op_addr + op_size) > (base + size))) { fprintf(stderr, "addr too large (%x, %x) for range (%x, %x)\n", op_addr, op_addr + op_size, base, base + size); return -1; } if ((op_addr < base) || ((op_addr + op_size) < base)) { fprintf(stderr, "addr too small (%x, %x) for range (%x, %x)\n", op_addr, op_addr + op_size, base, base + size); return -1; } return 0; } int do_out(struct uio_info_t *p, char *op, int addr, int val) { if (strcmp(op, "outb") == 0) { if (addr_in_range(p, addr, 1) < 0) return -1; outb(val, addr); } else if (strcmp(op, "outw") == 0) { if (addr_in_range(p, addr, 2) < 0) return -1; outw(val, addr); } else if (strcmp(op, "outl") == 0) { if (addr_in_range(p, addr, 4) < 0) return -1; outl(val, addr); } else { fprintf(stderr, "Invalid out operation\n"); return -1; } return 0; } int do_in(struct uio_info_t *p, char *op, int addr) { if (strcmp(op, "inb") == 0) { if (addr_in_range(p, addr, 1) < 0) return -1; printf("%x\n", inb(addr)); } else if (strcmp(op, "inw") == 0) { if (addr_in_range(p, addr, 2) < 0) return -1; printf("%x\n", inw(addr)); } else if (strcmp(op, "inl") == 0) { if (addr_in_range(p, addr, 4) < 0) return -1; printf("%x\n", inl(addr)); } else { fprintf(stderr, "Invalid in operation\n"); return -1; } return 0; } int main(int argc, char **argv) { struct uio_info_t *info_list, *p; /* TODO: make this safer */ char dev_name[16]; char *uioname, *op; int addr, val, ret = 0; if (argc < 4) { fprintf(stderr, "Not enough parameters\n"); return -1; } uioname = argv[1]; op = argv[2]; if (sscanf(argv[3], "%x", &addr) < 1) { fprintf(stderr, "Invalid address parameter\n"); return -1; } /* TODO: Add filter argument */ info_list = uio_find_devices(uio_filter); if (!info_list) { printf("No UIO devices.\n"); return -ENODEV; } p = info_list; while (p) { uio_get_all_info(p); uio_get_device_attributes(p); if(strcmp(p->name, uioname) == 0) break; p = p->next; } if (!p) { fprintf(stderr, "No UIO device with name: %s\n", uioname); return -ENODEV; } sprintf(dev_name,"/dev/uio%d", p->uio_num); if (uid_has_write_perms(dev_name) < 0) { fprintf(stderr, "Insufficient permissions\n"); return -EPERM; } if (iopl(3)) { fprintf(stderr, "Unable to iopl(3)\n"); return -EPERM; } if ((strcmp(op, "out") > 0) && (argc == 5)) { if (sscanf(argv[4], "%x", &val) < 1) { fprintf(stderr, "Invalid value parameter\n"); return -1; } ret = do_out(p, op, addr, val); } if ((strcmp(op, "in") > 0) && (argc == 4)) { ret = do_in(p, op, addr); } return ret; } ==================== uio: add the uio_aec driver
UIO driver for the Adrienne Electronics Corporation PCI time code device. This device differs from other UIO devices since it uses I/O ports instead of memory mapped I/O. In order to make it possible for UIO to work with this device a utility, uioport, can be used to read and write the ports. uioport is designed to be a setuid program and checks the permissions of the /dev/uio* node and if the user has write permissions it will use iopl and out*/in* to access the device. [1] git clone git://ifup.org/philips/uioport.git Signed-off-by: Brandon Philips <bran...@ifup.org> --- drivers/uio/Kconfig | 18 ++++ drivers/uio/Makefile | 1 drivers/uio/uio_aec.c | 177 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/uio_driver.h | 1 4 files changed, 197 insertions(+) Index: linux-2.6/drivers/uio/Makefile =================================================================== --- linux-2.6.orig/drivers/uio/Makefile +++ linux-2.6/drivers/uio/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_UIO_CIF) += uio_cif.o obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o obj-$(CONFIG_UIO_SMX) += uio_smx.o +obj-$(CONFIG_UIO_AEC) += uio_aec.o obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o Index: linux-2.6/include/linux/uio_driver.h =================================================================== --- linux-2.6.orig/include/linux/uio_driver.h +++ linux-2.6/include/linux/uio_driver.h @@ -91,5 +91,6 @@ extern void uio_event_notify(struct uio_ #define UIO_MEM_PHYS 1 #define UIO_MEM_LOGICAL 2 #define UIO_MEM_VIRTUAL 3 +#define UIO_MEM_PORT 4 #endif /* _LINUX_UIO_DRIVER_H_ */ Index: linux-2.6/drivers/uio/Kconfig =================================================================== --- linux-2.6.orig/drivers/uio/Kconfig +++ linux-2.6/drivers/uio/Kconfig @@ -58,6 +58,24 @@ config UIO_SMX If you compile this as a module, it will be called uio_smx. +config UIO_AEC + tristate "AEC video timestamp device" + depends on PCI + default n + help + + UIO driver for the Adrienne Electronics Corporation PCI time + code device. + + This device differs from other UIO devices since it uses I/O + ports instead of memory mapped I/O. In order to make it + possible for UIO to work with this device a utility, uioport, + can be used to read and write the ports: + + git clone git://ifup.org/philips/uioport.git + + If you compile this as a module, it will be called uio_aec. + config UIO_SERCOS3 tristate "Automata Sercos III PCI card driver" default n Index: linux-2.6/drivers/uio/uio_aec.c =================================================================== --- /dev/null +++ linux-2.6/drivers/uio/uio_aec.c @@ -0,0 +1,177 @@ +/* + * uio_aec.c -- simple driver for Adrienne Electronics Corp time code PCI device + * + * Copyright (C) 2008 Brandon Philips <bran...@ifup.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <asm/system.h> +#include <linux/uaccess.h> +#include <linux/uio_driver.h> + +#define PCI_VENDOR_ID_AEC 0xaecb +#define PCI_DEVICE_ID_AEC_VITCLTC 0x6250 + +#define INT_ENABLE_ADDR 0xFC +#define INT_ENABLE 0x10 +#define INT_DISABLE 0x0 + +#define INT_MASK_ADDR 0x2E +#define INT_MASK_ALL 0x3F + +#define INTA_DRVR_ADDR 0xFE +#define INTA_ENABLED_FLAG 0x08 +#define INTA_FLAG 0x01 + +#define MAILBOX 0x0F + +static struct pci_device_id ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ids); + +static irqreturn_t aectc_irq(int irq, struct uio_info *dev_info) +{ + void __iomem *int_flag = dev_info->mem[0].internal_addr + + INTA_DRVR_ADDR; + unsigned char status = ioread8(int_flag); + + + if ((status & INTA_ENABLED_FLAG) && (status & INTA_FLAG)) { + /* application writes 0x00 to 0x2F to get next interrupt */ + status = ioread8(dev_info->mem[0].internal_addr + MAILBOX); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void print_board_data(struct uio_info *i) +{ + printk(KERN_INFO "PCI-TC board vendor: %x%x number: %x%x" + " revision: %c%c\n", + ioread8(i->mem[0].internal_addr + 0x01), + ioread8(i->mem[0].internal_addr + 0x00), + ioread8(i->mem[0].internal_addr + 0x03), + ioread8(i->mem[0].internal_addr + 0x02), + ioread8(i->mem[0].internal_addr + 0x06), + ioread8(i->mem[0].internal_addr + 0x07)); +} + +static int __devinit probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct uio_info *info; + int ret; + + info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (pci_enable_device(dev)) + goto out_free; + + if (pci_request_regions(dev, "aectc")) + goto out_disable; + + info->name = "aectc"; + info->mem[0].addr = pci_resource_start(dev, 0); + if (!info->mem[0].addr) + goto out_release; + info->mem[0].internal_addr = pci_iomap(dev, 0, 0); + if (!info->mem[0].internal_addr) + goto out_release; + info->mem[0].size = pci_resource_len(dev, 0); + info->mem[0].memtype = UIO_MEM_PORT; + + info->version = "0.0.1"; + info->irq = dev->irq; + info->irq_flags = IRQF_DISABLED | IRQF_SHARED; + info->handler = aectc_irq; + + print_board_data(info); + ret = uio_register_device(&dev->dev, info); + if (ret) + goto out_unmap; + + iowrite32(INT_ENABLE, info->mem[0].internal_addr + INT_ENABLE_ADDR); + iowrite8(INT_MASK_ALL, info->mem[0].internal_addr + INT_MASK_ADDR); + if (ioread8(info->mem[0].internal_addr + INTA_DRVR_ADDR) + & INTA_ENABLED_FLAG) + printk(KERN_INFO "aectc: interrupts successfully enabled\n"); + + pci_set_drvdata(dev, info); + + return 0; + +out_unmap: + pci_iounmap(dev, info->mem[0].internal_addr); +out_release: + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); +out_free: + kfree(info); + return -ENODEV; +} + +static void remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + + /* disable interrupts */ + iowrite8(INT_DISABLE, info->mem[0].internal_addr + INT_MASK_ADDR); + iowrite32(INT_DISABLE, info->mem[0].internal_addr + INT_ENABLE_ADDR); + /* read mailbox to ensure board drops irq */ + ioread8(info->mem[0].internal_addr + MAILBOX); + + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + pci_set_drvdata(dev, NULL); + iounmap(info->mem[0].internal_addr); + + kfree(info); +} + +static struct pci_driver pci_driver = { + .name = "aectc", + .id_table = ids, + .probe = probe, + .remove = remove, +}; + +static int __init aectc_init(void) +{ + return pci_register_driver(&pci_driver); +} + +static void __exit aectc_exit(void) +{ + pci_unregister_driver(&pci_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(aectc_init); +module_exit(aectc_exit); |