babyking

  • 2019-03-15
  • 发表了主题帖: 芯灵思SinlinxA64开发板 Linux内核信号量学习

    在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。 自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"。但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"。鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。 一、信号量       信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:    (1) 测试控制该资源的信号量。    (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。    (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。    (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。     维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获得一个信号量ID。Linux2.6.26下定义的信号量结构体: semaphore.h        linux-3.5\include\Linux struct semaphore {         raw_spinlock_t                lock;         unsigned int                count;     //信号值,值为正数表示可以资源,0表示不能获得资源         struct list_head        wait_list; }; 初始化信号量 void sema_init (struct semaphore *sem, int val); 该函数初始化信号量,并设置信号量sem的值为val void init_MUTEX (struct semaphore *sem); 该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1,等同于sema_init (struct semaphore *sem, 1); void init_MUTEX_LOCKED (struct semaphore *sem); 该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,等同于sema_init (struct semaphore *sem, 0); 获得信号量 void down(struct semaphore * sem); 该函数用于获得信号量sem,它会导致睡眠(这个睡眠和下面所说的不知道有什么不同,既然不能被其它地方唤醒,那么这个down有什么用呢?),因此不能在中断上下文使用; int down_interruptible(struct semaphore * sem); 该函数功能与down类似,不同之处为,down不能被信号打断,但down_interruptible能被信号打断;(这个能被信号打断,有点疑惑,我现在做的项目是使用的是被中断打断,不知道它这个地方所说的是什么意思) int down_trylock(struct semaphore * sem); 该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。 释放信号量 void up(struct semaphore * sem); 该函数释放信号量sem,唤醒等待者。 2.互斥锁 2.1概念 互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。mutex实际上是count=1情况下的semaphore。 // 结构 struct mutex {         /* 1: unlocked, 0: locked, negative: locked, possible waiters */         atomic_t                  count;         spinlock_t                wait_lock;         struct list_head          wait_list; #ifdef CONFIG_DEBUG_MUTEXES         struct thread_info        *owner;         const char                *name;         void                      *magic; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC         struct lockdep_map         dep_map; #endif }; // 定义互斥锁lock mutex_init(struct mutex* lock)   或者直接用 #define DEFINE_MUTEX(LOCK)即可; // 获取 mutex_lock(struct mutex *lock) // 释放 mutex_unlock(struct mutex *lock) struct mutex lock; mutex_init(&lock);初始化互斥锁 或者直接用 #define DEFINE_MUTEX(LOCK)即可; #define __MUTEX_INITIALIZER(lockname) \         { .count = ATOMIC_INIT(1) \         , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \         , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \         __DEBUG_MUTEX_INITIALIZER(lockname) \         __DEP_MAP_MUTEX_INITIALIZER(lockname) } #define DEFINE_MUTEX(mutexname) \     struct mutex mutexname = __MUTEX_INITIALIZER(mutexname) extern void __mutex_init(struct mutex *lock, const char *name,              struct lock_class_key *key); 三:与自旋锁相关的API主要有: 定义自旋锁 spinlock_t spin; 初始化自旋锁 spin_lock_init(lock) 该宏用于动态初始化自旋锁lock 获得自旋锁 spin_lock(lock) 该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放; spin_trylock(lock) 该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再"在原地打转"; 释放自旋锁 spin_unlock(lock) 该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用; 除此之外,还有一组自旋锁使用于中断情况下的API。 参考博客:https://www.cnblogs.com/biaohc/p/6679195.html 此内容由EEWORLD论坛网友babyking原创,如需转载或用于商业用途需征得作者同意并注明出处

  • 2019-03-14
  • 发表了主题帖: 芯灵思Sinlinx A64开发板设置qt程序自启动

    开发平台 芯灵思Sinlinx A64 内存: 1GB   存储: 4GB 此内容由EEWORLD论坛网友babyking原创,如需转载或用于商业用途需征得作者同意并注明出处 对于开发板开机启动程序的设置可以这样做通过串口连接开发板 #  vi /etc/profile http://bbs.elecfans.com/data/attachment/forum/201812/12/143657xf39nd93377n37it.png.thumb.jpg 可以看到黄色框的就是qt自启动的命令,仿照这个替换为自己的qt程序 我的qt程序在 /root/01 程序名字为01 http://bbs.elecfans.com/data/attachment/forum/201812/12/143658gc8j88m8icl8m8i6.png.thumb.jpg 最后开发板上电,可以看到启动程序是自己的qt程序

  • 2019-03-13
  • 发表了主题帖: 芯灵思Sinlinx A64开发板 Linux内核等待队列poll ---阻塞与非阻塞

    开发平台 芯灵思Sinlinx A64 内存: 1GB   存储: 4GB 阻塞:阻塞调用是指调用结果返回之前,当前进程程会被挂起(休眠)。函数只有在得到结果之后才会返回。默认情况下,文件都是以这种方式打开。 非阻塞:指在不能立刻得到结果之前,该函数不会阻塞当前进程程,而会立刻返回。应用程序可选择以阻塞或非阻塞方式打开设备文件,然后设备进行读写操作,如果驱动的读写函数支持阻塞和非阻塞功能,这两种打开方式才会有区别。 阻塞示例 :fd = open("/xxx/word", O_RDONLY ); // 默认阻塞方式打开         如果此时没有数据可以读取,则执行休眠          如果有数据可以读取,则马上读取数据,不休眠,读取数据后马上返回。 非阻塞示例 :fd = open("/xxx/word", O_RDONLY | O_NONBLOCK ); //非阻塞方式打开         如果此时已经有数据可以读取,则读取数据再返回。         如果没有数据可以读,也马上返回,但是返回一个错误码。 1)驱动中如何得到用户空间应用程序打开的方式?                 open一个设备,内核会创建一个file结构,并且把打开方式的数值存放到file结构成员f_flags成员中,驱动程序的read,write 接口可以使用参数file指针取得文件打开方式。file结构中有一个成员是f_flags ,创建时候,内核会把open 函数的最后一个参数 flag 数值保存在 f_flags 变量中。 static ssize_t xxx_read(struct file *pfile, char user *buf, size_t count, loff_t *poff) {         ……         //判断当前是否有按键动作         if(没有按键动作)     {                 //判断 pfile->f_flags 成员是否设置 O_NONBLOCK                   if(pfile->f_flags & O_NONBLOCK) //表示用户空间使用非阻塞打开                 {                         return - EAGAIN; //返回一个错误码,告诉用户空间你可以再尝试读取                 }                 //阻塞方式打开,没有数据就休眠,不马上返回else                 {                         //休眠,等待有按键动作唤醒进程。                 }         } } 2)如何知道是否有按键动作?         如果按键按键或松开时刻,会产生一个中断,所以,在中断程序设置一个标志即可。         定义一个全局变量,初始值为 0,表示没有按键动作发生,在中断程序中设置这个变量值为 1,表示发生按键动作。 3)如何让进程进入休眠状态?         最简单,最直接的休眠方式: msleep 函数         这个函数:一旦调用,则调用进程会休眠指定长的时间,时间一到内核会唤醒这个进程.         //休眠,等待有按键动作唤醒进程。     while(press == 0)                  msleep(5); // 休眠5ms

  • 2019-03-11
  • 发表了主题帖: 芯灵思Sinlinx A64开发板Linux内核定时器编程

    开发平台 芯灵思Sinlinx A64 内存: 1GB   存储: 4GB 开发板详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 Linux 内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 和 kernel/timer.c 文件中。 内核定时器的数据结构 struct timer_list {     struct list_head entry;  //双向链表元素list:用来将多个定时器连接成一条双向循环队列。     unsigned long expires; //expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。     void (*function)(unsigned long); //指向一个可执行函数。当定时器到期时,内核就执行function所指定的函数。而data域则被内核用作function函数的调用参数。     unsigned long data;     struct tvec_base *base;     /* ... */ }; 时间比较操作 在定时器应用中经常需要比较两个时间值,以确定timer是否超时,所以Linux内核在timer.h头文件中定义了4个时间关系比较操作宏。这里我们说时刻a在时刻b之后,就意味着时间值a≥b。 Linux强烈推荐用户使用它所定义的下列4个时间比较操作宏(include/linux/timer.h): #define time_after(a,b) ((long)(b) - (long)(a) < 0) #define time_before(a,b) time_after(b,a) #define time_after_eq(a,b) ((long)(a) - (long)(b) >= 0) #define time_before_eq(a,b) time_after_eq(b,a) Linux 内核时间相关转换函数 1. unsigned long usecs_to_jiffies(const unsigned int u) 功能: 把微秒转换成时钟节拍 参数: u 时间微秒 返回: 对应的时钟节拍数量 2. unsigned long msecs_to_jiffies(const unsigned int m) 功能: 把毫秒转换成时钟节拍 参数: u 时间毫秒 返回: 对应的时钟节拍数量 示例: 要定时从现在开始, 3 毫秒执行一个函数 expires 设置为 jiffies+ msecs_to_jiffies(3) Linux 内核定时器操作相关 API 1. 静态定义结构体变量并且初始化(宏) DEFINE_TIMER(_name, _function, _expires, _data) 功能: 定义一个名字为_name 的 struct timer_list 结构的变量, 并且初始化它的 function, expires, data 成员 2. 定时器初始化(宏) init_timer(timer) 功能: 只是对 struct timer_list 结构成员进行一些基础初始化操作, function, expires, data 成员还需要用户自 己填充。 3.设置定时器(宏) setup_timer(timer, fn, data) 功能: 设置定时器中的 function, data 和一些基础成员, expires 并没有初始化, 需要用户自己进行初始化 4. 注册定时器到内核 void add_timer(struct timer_list *timer) 功能: 向内核注册一个定时器, 注册后会马上开始计时。 5.从内核注销定时器 int del_timer(struct timer_list * timer); 功能: 从内核定时链表上删除指定的定时器, 删除后就不会再执行绑定的函数 6. 修改定时器定时时间值, 并且重新注册 int mod_timer(struct timer_list *timer, unsigned long expire0.s); 功能: 修改定时器定时时间值, 并且重新注册, 不管这个定时的超时函数是否执行过。 执行完成后会马上启 动定时。 内核定时器编程步骤 Step1 定义timer_list 结构变量 Step2 定义超时函数 Step3 对timer_list结构变量进行初始化 Step4 注册定时器,启动定时 Step5 注销定时器 驱动代码: #include #include #include //Step1 timer_list 结构变量 struct timer_list timer; //Step2 超时函数 void timer_fun(long data) {                 printk("%s is call! data:%d\r\n",__FUNCTION__,data);//__FUNCTION__   获取当前函数名                 mod_timer(&timer, jiffies + HZ*1); //再次修改本定时器超时时间为当前时间后1秒 } static int __init timer_init(void) {                 //Step3 对timer_list结构变量进行初始                 init_timer(&timer);                 setup_timer(&timer, timer_fun, 666);                 timer.expires = jiffies + HZ*2;                 //Step4 注册定时器,启动定时                 add_timer(&timer);                 printk("Timer start!\r\n");                 return 0; } static void __exit timer_exit(void) //Module exit function specified by module_exit() {                 //Step5 注销定时器                 del_timer_sync(&timer);                 printk("Timer over!\r\n"); } module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL");复制代码 Makefile代码: KERN_DIR = /work/lichee/linux-3.4 all:         make -C $(KERN_DIR) M=`pwd` modules clean:         make -C $(KERN_DIR) M=`pwd` modules clean         rm -rf modules.order obj-m        += timer_drv.o复制代码 最后使用 dmseg 命令查看,可以看到每隔1秒打印一次 http://bbs.elecfans.com/data/attachment/forum/201902/14/150433wn01zqyj5hjz0513.png.thumb.jpg

  • 2019-03-06
  • 发表了主题帖: 芯灵思Sinlinx A64 linux 通过设备树写LED驱动(附参考代码,未测试)

    开发平台 芯灵思Sinlinx A64 内存: 1GB   存储: 4GB 详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 此内容由EEWORLD论坛网友babyking原创,如需转载或用于商业用途需征得作者同意并注明出处 全志A64设备树结构体 #include //设备树里的每个设备及每个设备子节点都用此结构体描述 struct device_node {     const char *name;     const char *type;     phandle phandle;     const char *full_name;     struct property *properties; //属性     struct property *deadprops; /* removed properties */     struct device_node *parent; //在设备子节点对象,指向属于的设备对象     struct device_node *child; //在设备对象,指向子节点     struct device_node *sibling; //指向同级的下一个对象.     struct device_node *next; /* next device of same type */ //应是指向device_type是同样的对象     struct device_node *allnext; /* next in list of all nodes */ ... }; //下面函数用于获取设备树里的设备节点及设备子节点 extern struct device_node *of_find_node_by_name(struct device_node *from, const char *name); //通过名字查找相应的设备节点 static inline int of_get_child_count(const struct device_node *np); //获取指定设备的子节点个数 extern struct device_node *of_find_node_by_path(const char *path); //通过路径来获取设备节点,可用于获取设备子节点 extern struct device_node *of_find_node_by_type(struct device_node *from, const char *type);  //通过指定的device_type来获取设备节点 //下面函数用于获取设备节点或设备子节点的属性 static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) extern int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value); extern int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz); extern int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz); extern int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz); extern int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value); extern int of_property_read_string(struct device_node *np, const char *propname, const char **out_string) 首先增加节点,修改dtsi文件。 vim /lichee/linux-3.10/arch/arm64/boot/dts/sun50iw1p1-pinctrl.dtsi gpio = ; |            |    |  |   |  |  | |-------------------表示有效电平 |            |    |  |   |  |  |----------------------上下拉, 0关闭功能, 1上拉, 2下拉, 3保留 |            |    |  |   |  |-------------------------驱动力,电流等级(0 - 3),级别越高,输出电流越大 |            |    |  |   |----------------------------gpio功能类型,0输入, 1输出, 6和外部中断,7关闭功能(具体查手册) |            |    |  |------------------------------pin bank 内偏移(即组内第几个io口). |            |    |---------------------------------哪组gpio,PA(0),PB(1),PC(2),PD(3),PE(4),PF(5),PG(6),PH(7),PI(8),PJ(9),PK(10),PL(11) |            |--------------------------------------指向哪个gpio控制器, pio / r_pio(PL组) |-----------------------------------------------------属性名字(随便命名) 驱动代码: #include #include #include #include #include #include #include #include #include #include #include #include #define MY_DEVICE_NAME "my_led_device" // 获取到设备树中到节点 static int gpio = -1; int get_irqno_from_node(void) {                 struct gpio_config config;         struct device_node *np = of_find_node_by_path("/leds");         if(np){                 printk("find node ok\n");         }         else{                 printk("find node failed\n");         }         gpio = of_get_named_gpio_flags(nd, "gpios", i, (enum of_gpio_flags *)&config);// 从设备树中读取gpios的GPIO配置编号和标志         if(!gpio_is_valid(gpio)){                 //判断该 GPIO 编号是否有效,有效gpio_request 则申请占用该 GPIO。如果初始化过程出错,需要调用 gpio_free 来释放之前申请过且成功的 GPIO                 printk("gpio isn't valid\n");                 return -1;         }         if(gpio_request(gpio, "leds") < 0)                 printk("gpio request failed %d\n", gpio);         gpio_direction_output(gpio, 1); //关灯                         return 0; } static int my_open (struct inode *node, struct file *filp) {     if(gpio)     {                 printk("open ok\n");     }     else     {         return -EINVAL;     }     return 0; } static ssize_t my_write (struct file *filp, const char __user *buf, size_t size, loff_t *off) {     unsigned char val;             copy_from_user(&val, buf, 1);     printk(" gpl_dat address   0x%x\n",gpl_dat);     if (val)     {                    gpio_direction_output(gpio, 0); //关灯                 printk("led on\n");     }     else     {        gpio_direction_output(gpio, 1); //关灯                 printk("led off\n");     }     return 1; } static const struct file_operations my_led_fops = {     //step 1 :定义file_operations结构体     .open = my_open,     .write = my_write,    }; //step 1 : static struct class *led_class; static struct cdev *pcdev;      //定义一个cdev指针 static dev_t n_dev;            //第一个设备号(包含了主和次) static int __init led_device_init(void) {//step 2 :注册     int ret = -1;     pcdev = cdev_alloc();//分配cdev结构空间     if(pcdev == NULL) {         printk(KERN_EMERG" cdev_alloc  error\n");         ret = -ENOMEM;   /* 分配失败 */         return ret;     }     //2. 动态申请设备号     ret = alloc_chrdev_region(&n_dev, 0 , 2, MY_DEVICE_NAME);     if(ret < 0 ) {         //释放前面成功的资源         kfree(pcdev);                              /*释放cdev结构空间 */         printk(KERN_EMERG"alloc_chrdev_region  error\n");         return ret;     }        cdev_init(pcdev, &my_led_fops);     //初始化cdev结构           /* 建立cdev和file_operations之间的连接 */     /*         或这样初始化cdev结构         pcdev->owner = THIS_MODULE;         pcdev->ops = &my_led_fops;     */     ret = cdev_add(pcdev, n_dev, 2) ;// 向内核里面添加一个驱动,注册驱动     if(ret < 0 ) {         //释放前面成功的资源         unregister_chrdev_region(n_dev,  2);       /*  释放前面申请的调和号*/         kfree(pcdev);                               /* 释放cdev结构空间 */         printk(KERN_EMERG"alloc_chrdev_region  error\n");         return ret;     }     /*自动创建设备节点/dev/SinlinxA64_LED*/     led_class = class_create(THIS_MODULE, "myled");        device_create(led_class, NULL, n_dev, NULL, "SinlinxA64_LED");         get_irqno_from_node();     printk(KERN_EMERG"cdev ok\n");        return 0; } static void __exit led_device_exit(void) {    //step 2 :注销     //注销cdev结构     cdev_del(pcdev);     //释放设备号     unregister_chrdev_region(n_dev, 2); /*起始设备号(主、次) 连续的次设备号数量*/     //释放cdev结构空间     kfree(pcdev);         device_destroy(led_class, n_dev);     class_destroy(led_class);     gpio_free(gpio);     printk(KERN_EMERG"cdev_del ok\n"); } module_init(led_device_init); module_exit(led_device_exit); MODULE_LICENSE("GPL");复制代码 参考文章:https://blog.csdn.net/jklinux/article/details/82382066

  • 2019-03-05
  • 发表了主题帖: 芯灵思SinlinxA33开发板Linux总线设备驱动实现过程(附代码)

    开发平台 芯灵思Sinlinx A33 内存: 1GB   存储: 4GB 详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 总线驱动设备模型: 1、总线驱动设备模型只是提供一种机制,将驱动程序分为device和driver两部分并彼此建立联系2、注册device过程:    a、将device放入bus的dev链表    b、从bus的drv链表取出每一个driver,用bus的match函数判断取出的driver能否支持这个device(判断name)    c、如果支持,调用该driver的probe函数(probe函数自由实现)3、注册driver过程:    a、将driver放入bus的drv链表    b、从bus的dev链表取出每一个device,用bus的match函数判断这个driver能否支持取出的device(判断name)    c、如果支持,调用该driver的probe函数(probe函数自由实现) 在linux系统总线存在目录/sys/bus/ 总线结构体:描述一个总线,管理device和driver,完成匹配 struct bus_type {     const char        *name;                                                    /*总线名*/     const char        *dev_name;     struct device        *dev_root;     struct bus_attribute    *bus_attrs;     struct device_attribute    *dev_attrs;//设备属性     struct driver_attribute    *drv_attrs;//驱动属性     int (*match)(struct device *dev, struct device_driver *drv); //设备驱动匹配函数     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);     int (*probe)(struct device *dev);     int (*remove)(struct device *dev);     void (*shutdown)(struct device *dev);     int (*suspend)(struct device *dev, pm_message_t state);     int (*resume)(struct device *dev);     const struct dev_pm_ops *pm;     struct iommu_ops *iommu_ops;     struct subsys_private *p; }; 总线注册和注销     int bus_register(struct bus_type *bus)     void bus_unregister(struct bus_type *bus) /=================================================================/ device对象 struct device {     struct kobject kobj;  //所有对象的父类     const char        *init_name; // 在总线中会有一个名字,用于做匹配     struct bus_type    *bus; //指向该device对象依附于总线的对象     void        *platform_data; // 自定义的数据,指向任何类型数据 } device对象注册和注销的方法:     int device_register(struct device *dev)     void device_unregister(struct device *dev) /=================================================================/ driver对象:描述设备驱动的方法(代码逻辑) struct device_driver {     const char        *name;     // 在总线中会有一个名字,用于做匹配,在/sys/bus/mybus/drivers/名字     struct bus_type        *bus;//指向该driver对象依附于总线的对象     int (*probe) (struct device *dev); // 如果device和driver匹配之后,driver要做的事情     int (*remove) (struct device *dev); // 如果device和driver从总线移除之后,driver要做的事情 } driver对象注册和注销:     int driver_register(struct device_driver *drv)     void driver_unregister(struct device_driver *drv) 总线代码: #include #include #include int mybus_match(struct device *dev, struct device_driver *drv) {         //如果匹配成功,match方法一定要返回一个1, 失败返回0         if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))         {                 printk("match ok\n");                 return 1;         }else{                 printk("match failed\n");                 return 0;         }         return 0; } //实例化一个bus对象 struct bus_type mybus = {         .name = "mybus",         .match = mybus_match, }; EXPORT_SYMBOL(mybus);//使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用 static int __init mybus_init(void) {         printk("----------%s-------------\n", __FUNCTION__);         int ret;         //构建一个总线         // /sys/bus/mybus         ret = bus_register(&mybus);         if(ret != 0)         {                 printk("bus_register error\n");                 return ret;         }         return 0; } static void __exit mybus_exit(void) {         printk("----------%s-------------\n", __FUNCTION__);         bus_unregister(&mybus);         } module_init(mybus_init); module_exit(mybus_exit); MODULE_LICENSE("GPL"); 复制代码 driver代码: #include #include #include #include int mydrv_probe(struct device *dev) {         printk("----------%s-------------\n", __FUNCTION__);                 return 0; } int mydrv_remove(struct device *dev) {         printk("----------%s-------------\n", __FUNCTION__);         return 0; } extern struct bus_type mybus; struct device_driver mydrv = {         .name = "my_dev_drv",         .bus = &mybus,         .probe = mydrv_probe,         .remove = mydrv_remove, }; static int __init mydrv_init(void) {         printk("----------%s-------------\n", __FUNCTION__);         //将driver注册到总线中         int ret;         ret  = driver_register(&mydrv);         if(ret < 0)         {                 printk("device_register error\n");                 return ret;         }                 return 0; } static void __exit mydrv_exit(void) {         printk("----------%s-------------\n", __FUNCTION__);         driver_unregister(&mydrv); } module_init(mydrv_init); module_exit(mydrv_exit); MODULE_LICENSE("GPL"); 复制代码 device代码: #include #include #include #include "dev_info.h" extern struct bus_type mybus; void        mydev_release(struct device *dev) {         printk("----------%s-------------\n", __FUNCTION__); } //构建一个device对象 struct device  mydev= {         .init_name = "my_dev_drv",         .bus = &mybus,         .release = mydev_release, }; static int __init mydev_init(void) {         printk("----------%s-------------\n", __FUNCTION__);         //将device注册到总线中         int ret;         ret  = device_register(&mydev);         if(ret < 0)         {                 printk("device_register error\n");                 return ret;         }                 return 0; } static void __exit mydev_exit(void) {         printk("----------%s-------------\n", __FUNCTION__);         device_unregister(&mydev); } module_init(mydev_init); module_exit(mydev_exit); MODULE_LICENSE("GPL"); 复制代码 --------------------- 参考文章https://blog.csdn.net/hfutyyj/article/details/80248904

  • 2019-02-26
  • 发表了主题帖: 芯灵思Sinlinx A33开发板boa与CGI移植

             在嵌入式设备的管理与交互中,基于Web方式的应用成为目前的主流,这种程序结构也就是大家非常熟悉的B/S结构,即在 嵌入式设备上运行一个支持脚本或CGI功能的Web服务器, 能够生成动态页面,在用户端只需要通过Web浏览器就可以对嵌入式设备进行管理和监控,非常方 便实用。本节主要介绍这种应用的开发和移植工作。用户首先需要在嵌入式设备上成功移植支持脚本或CGI功能的Web服务器,然后才能进行应用程序的开发。 1、 嵌入式Web服务器移植  由于嵌入式设备资源一般都比较有限,并且也不需要能同时处理很多用户的请求,因此不会使用Linux下最常用的如Apache 等服务器,而需要使用一些专门为嵌入式设备设计的Web服务器, 这些Web服务器在存贮空间和运行时所占有的内存空间上都会非常适合于嵌入式应用场合。典型的嵌入式Web服务器有Boa (www.boa.org)和thttpd (http://www.acme.com/software/thttpd/)等, 它们和Apache等高性能的Web服务器主要的区别在于它们一般是 单进程服务器,只有在完成一个用户请求后才能响应 另一个用户的请求,而无法并发响应,但这在嵌入式设备的应用场合里已经足够了。      我们绍比较常用的Boa服务器的移植。      Boa是一个非常小巧的Web服务器,可执行代码只有约60KB。它是一个单任务Web服务器,只能依次完成用户的请求, 而不会fork出新的进程来处理 并发连接请求。但Boa支持CGI,能够为CGI程序fork出一个进程来执行。Boa的设计目标 是速度和安全,在其站点公布的性能测试中,Boa的性能 要好于Apache服务器。     第一步完成Boa程序的移植。从www.boa.org下载Boa源码,当前最新版本为0.94.13,将其解压并进入源码目录的src 子目录     # tar xzf boa-0.94.13.tar.gz     # cd boa-0.94.13/src 生成Makefile文件     # ./configure     修改Makefile文件,找到CC=gcc,将其改成CC = arm-none-linux-gnueabi-gcc,再找到CPP = gcc –E, 将其改成CPP = arm-none-linux-gnueabi-gcc –E,并保存退出。     然后运行make进行编译,得到的可执行程序为boa,将调试信息剥去,得到的最后程序只有约60KB大小。     # make     # arm-none-linux-gnueabi-strip boa    第二步完成Boa的配置,使其能够支持CGI程序的执行。Boa需要在/etc目录下建立一个boa目录,里面放入Boa的主要 配置文件boa.conf。在Boa源码目录下已有一个示例boa.conf,可以在其基础上进行修改,下面解释一下该文件的含义: #监听的端口号,缺省都是80,一般无需修改 Port 80 # bind调用的IP地址,一般注释掉,表明绑定到INADDR_ANY,通配于服务器的所有IP地址 #Listen 192.68.0.5 #作为哪个用户运行,即它拥有该用户的权限,一般都是nobody,需要/etc/passwd中有 #nobody用户 User nobody #作为哪个用户组运行,即它拥有该用户组的权限,一般都是nogroup,需要在/etc/group文 #件中有nogroup组 Group nogroup #当服务器发生问题时发送报警的email地址,目前未用,注释掉 #ServerAdmin root@localhost #错误日志文件。如果没有以/开始,则表示从服务器的根路径开始。如果不需要错误日志, 则用#/dev/null。在下面设置时,注意一定要建立/var/log/boa目录 ErrorLog /var/log/boa/error_log #访问日志文件。如果没有以/开始,则表示从服务器的根路径开始。如果不需要错误日志, 则用#/dev/null或直接注释掉。在下面设置时,注意一定要建立/var/log/boa目录 #AccessLog /var/log/boa/access_log #是否使用本地时间。如果没有注释掉,则使用本地时间。注释掉则使用UTC时间 #UseLocaltime #是否记录CGI运行信息,如果没有注释掉,则记录,注释掉则不记录 #VerboseCGILogs #服务器名字 ServerName www.hyesco.com #是否启动虚拟主机功能,即设备可以有多个网络接口,每个接口都可以拥有一个虚拟的Web服 #务器。一般注释掉,即不需要启动 #VirtualHost #非常重要,HTML文档的主目录。如果没有以/开始,则表示从服务器的根路径开始。 DocumentRoot /var/www #如果收到一个用户请求的话,在用户主目录后再增加的目录名 UserDir public_html #HTML目录索引的文件名,也是没有用户只指明访问目录时返回的文件名 DirectoryIndex index.html #当HTML目录没有索引文件时,用户只指明访问目录时,boa会调用该程序生成索引文件然后 #返回给用户,因为该过程比较慢最好不执行,可以注释掉或者给每个HTML目录加上#DirectoryIndex指明的文件 #DirectoryMaker /usr/lib/boa/boa_indexer #如果DirectoryIndex不存在,并且DirectoryMaker被注释,那么就用Boa自带的索引 #生成程序来生成目录的索引文件并输出到下面目录,该目录必须是Boa能读写 # DirectoryCache /var/spool/boa/dircache #一个连接所允许的HTTP持续作用请求最大数目,注释或设为0都将关闭HTTP持续作用 KeepAliveMax 1000 #HTTP持续作用中服务器在两次请求之间等待的时间数,以秒为单位,超时将关闭连接 KeepAliveTimeout 10 #指明mime.types文件位置。如果没有以/开始,则表示从服务器的根路径开始。可以注释掉 #避免使用mime.types文件,此时需要用AddType在本文件里指明 MimeTypes /etc/mime.types #文件扩展名没有或未知的话,使用的缺省MIME类型 DefaultType text/plain #提供CGI程序的PATH环境变量值 CGIPath /bin:/usr/bin:/usr/local/bin #将文件扩展名和MIME类型关联起来,和mime.types文件作用一样。如果用mime.types #文件,则注释掉,如果不使用mime.types文件,则必须使用 #AddType application/x-httpd-cgi cgi #指明文档重定向路径 #Redirect /bar http://elsewhere/feh/bar #为路径加上别名 Alias /doc /usr/doc #非常重要,指明CGI脚本的虚拟路径对应的实际路径。一般所有的CGI脚本都要放在实际路径 #里,用户访问执行时输入站点+虚拟路径+CGI脚本名 ScriptAlias /cgi-bin/ /var/www/cgi-bin/    用户可以根据自己需要,对boa.conf进行修改,但必须要保证其他的辅助文件和设置必须和boa.conf里的配置相符, 不然Boa就不能正常工作。 /**************************************************************************************************/ 一、CGIC简介 1、CGI简介         CGI(Common Gateway Interface)是外部应用扩展应用程序与WWW服务器交互的一个标准接口。按照CGI标准编写的外部扩展应用程序可以处理客户端浏览器输入的数据,从而完成客户端与服务器的交互操作。而CGI规范就定义了Web服务器如何向扩展应用程序发送消息,在收到扩展应用程序的信息后又如何进行处理等内容。通 过CGI可以提供许多静态的HTML网页无法实现的功能,比如搜索引擎、基于Web的数据库访问等等。 CGI的主要功能:     A、分析数据,并自动校正一些有缺陷的浏览器发来的数据     B、透明接收用GET或 POST方法发来的From数据     C、能接受上传文件     D  、能够设置和接收cookies     E、用一致的方式处理From元素里的回车     F、提供字符串,整数,浮点数,单选或多选功能来接收数据     G、提供数字字段的边界检查     H、能够将CGI环境变量转化成C中的非空字符串     I、提供CGI程序的调试手段,能够回放CGI程序执行时的CGI状态 2、BOA与CGI工作机制     BOA和CGI的工作机制:         HTTP协议是WWW的基础,基于客户/服务器模型,服务器可以为分布在网络中的客户提供服务。HTTP是建立在TCP/IP协议之上的“无连接”协议,每次连接只处理一个请求。在BOA服务器上,运行产着一个守护进程对端口进行监听,等待来自客户的请求。当一个请求到来时,将创建一个子进程为用户的连接服务。根据请求的不同,服务器返回HTML文件或者通过CGI调用外部应用程序,返回处理结果。服务器通过CGI与外部程序和脚本之间进行交互,根据客户端在进行请求时所采取的方法,服务器会收集客户所提供的信息,并将该部分信息发送给指定的CGI扩展程序。CGI扩展程序进行信息处理并将结果返回服务器,然后服务器 对信息进行分析,并将结果发送回客户端。     外部CGI程序与BOA服务器进行通信、传递有关参数和处理结果是通过环境变量、命令行参数和标准输入来进行的。服务器提供了客户端(浏览器)与CGI扩展程序之间的信息交换的通道。CGI的标准输入是服务器的标准输出,而CGI的标准输出是服务器的标准输入。客户的请求通过服务器的标准输出传送给CGI的标准输入,CGI对信息进行处理后,将结果发送到它的标准输入,然后由服务器将处理结果发送给客户端。          CGIC是一个功能比较强大的支持CGI开发的标准C库,并支持Linux,Unix 和Windows等多操作系统。         CGIC的主站点http://www.boutell.com/cgic/ 3、URL简介     客户端浏览器向服务器发送数据采用编码的形式进行,编码就是URL编码。编码的主要工作是表单域的名字和值的转义,具体的做法为:每一对域和值里的空格都会被替换为一个加号(+)字符,不是字母或数字的字符将被替换为它们的十六进制数字形式,格式为%HH。HH是字符的ASCII十六进制值。标签将被替换为“%0D%0A”。     信息是按它们在表单里出现的顺序排列的。数据域的名字和数据域的值通过等号(=)字符连在一起。各对名/值再通过“&”字符连接在一起。经过这些编码处理之后,表单信号就整个成为一个连续的字符流,里面包含着将被送往服务器的全部信息。     因为表单输入信息都是经过编码后传递给脚本程序的,所以CGI扩展程序在使用这些参数之前必须对它们进行解码。 二、CGIC编译配置 1、下载CGIC源码     tar -zxvf cgic206.tar.gz 2、修改Makefile文件 CC=arm-none-linux-gnueabi--gcc AR=arm-none-linux-gnueabi--ar RANLIB=arm-none-linux-gnueabi-ranlib CFLAGS=-g -Wall -static cgictest.cgi: cgictest.o libcgic.a       $(CC) $(CFLAGS) cgictest.o -o cgictest.cgi ${LIBS} capture: capture.o libcgic.a       $(CC) $(CFLAGS) capture.o -o capture ${LIBS} 3、编译     make     编译得到的文件     libcgic.a:CGIC库     capture:调试辅助程序     cgictest.cgi:测试程序 4、安装CGIC     make install     CGIC安装路径为     libcgic.a 安装在/usr/local/lib     cgic.h 安装在/usr/local/include     CGIC库安装后就可以使用CGIC编程了 5、CGIC文件的移植     将capture和cgictest.cgi拷贝到开发板的/var/www/cgi-bin目录 6、运行cgi程序     在客户端浏览器运行http://192.168.6.210/cgi-bin/cgictest.cgi     如果正常显示网页内容,则BOA与CGIC可以正常工作 三、CGIC移植过程中错误的解决 1、html网页可以运行,CGI程序运行报错     Boa服务器报错:cgi_header: unable to find LFLF     客户端浏览器报错:502 Bad Gateway                 The CGI was not CGI/1.1 compliant.     解决方法:静态编译cgi程序     arm-linux-gcc -o hello.cgi hello.c -static 四、CGIC编程 1、CGI通信方式     当有数据从客户端浏览器传到Web服务器后,web服务器会根据传送的类型(基本有二类:GET/POST),将接收到的数据传入 QUERY_STRING或变量中, CGI程序可以通过标准输入,在程序中接收web服务器接收的数据。当要向浏览器发送信息时,只要向Web服务器发送特定的文件头信息,即可通过标准输出将信息发往Web服务器, Web服务器处理完由CGI程序发来的信息后就会将信息发送给浏览器。 2、接收数据     用GET方式接收到的数据保存在Web服务器的QUERY_STRING 变量里,而通过POST方式接收到的数据是保存在Web服务器变量里。两种数据接收方式的区别是:以GET方式接收的数据是有长度限制,而用POST方式接收的数据是没有长度限制的;以GET方式发送数据,可以通过URL的形式来发送,但POST方式发送的数据必须要通过Form才到发送。 3、CGI变量 char *cgiServerSoftware     服务器软件名称,或者一个空的字符串 char *cgiServerName     返回服务器名称或空 char *cgiGatewayInterface     网关接口(通常是 CGI/1.1)或空 char *cgiServerProtocol     网络协议(usually HTTP/1.0)或空 char *cgiServerPort     服务器端口(usually 80),或空 char *cgiRequestMethod     请求方式(usually GET or POST)或空 char *cgiPathInfo     指出附加虚拟路径 char *cgiPathTranslated     指出附加虚拟路径并由服务器转为本地路径 char *cgiscriptName     调用程序的名字 char *cgiQueryString     包含GET-method请求或者 标签。不需解析,除非用标签,通常由CGIC函数库自动解析。 char *cgiRemoteHost     从浏览器返回客户主机的名字 char *cgiRemoteAddr     从浏览器返回客户的IP地址 char *cgiAuthType     返回用户授权信息 char *cgiRemoteUser     鉴别用户cgiAuthType. char *cgiRemoteIdent     返回用户的名字(用户通过用户坚定协议) char *cgiContentType     返回MIME内型 char *cgiAccept     参考 cgiHeaderContentType() cgiUserAgent char *cgiUserAgent     获取的用户浏览器信息 char *cgiReferrer     指向用户访问的URL. int cgiContentLength     表单或查询数据的字节被认为是标准的. FILE *cgiOut     CGI输出。cgiHeader函数,象cgiHeaderContentType,首先被用于输出mime头;用于 fprintf() 和fwrite()。cgiOut通常相当于stdout。 FILE *cgiIn     CGI输入 4、CIGC库主要函数     用一般 ANSI C或C++编译器就可以编译CGIC程序 , 与C程序不同的是,用CGIC写的源码其主函数是cgiMain(), 而不是通常的main。 CGIC的函数库会自动把cgiMain连接到相应的main上。     CGIC库主要函数说明:     cgiFormResultType cgiFormString( char *name, char *result, int max)     用于从输入域中copy字符串。将域名max-1字节中的字符copy到缓冲区result。若域不存在,则copy一个空串到result缓冲区。在此函数中所有的新行由换行符代表。     cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max)     与cgiFormString函数相似,只是所有的CR和LF都被去掉。      cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length)     返回指向name的字符串的长度,并将长度放入length中。     cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray)     若同一名字有多个输入域,或域中的字符串可以动态变化,使用本函数。它把名为name的所有输入域的值放在prtToStringArray中。     void cgiStringArrayFree(char **stringArray)     释放了分配给stringArray的内存。     cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV)     从输入域中取出整数放入result中。     cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV)     若输入域中的整数在界限内则取出并放入result中。     cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV)     从输入域中取出浮点数放入result中。     cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV)     若输入域中的浮点数在界限内则取出并放入result中。     cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV)     取出复选框(跟在select语句之后的),把选择的名字copy到choicesText,把选择的个数copy到choicesTotal,把当前的选择copy到result。     cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid)     与cgiFormSelectSingle类似,只指向整型数组的result代表了选择的项。     cgiFormResultType cgiFormCheckboxSingle( char *name)     若复选框被选中,则函数返回cgiFormSuccess,否则返回cgiFormNotFound。     cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid)     与cgiFormCheckboxSingle类似,但它处理同一名字有多个复选框的情况。name指向复选框的名字;valuesText指向包含有每个复选框中参数的一个数组;valuesTotal指向复选框的总数;result是一个整型数组,每个复选框选中的用1代表,没选中的用0代表。     cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV)     与cgiFormCheckboxMultiple相似,只是这里是单选按钮而不是复选框。     void cgiHeaderLocation(char *redirectUrl)     重定向到redirectUrl指定的URL。     void cgiHeaderStatus(int status, char *statusMessage)     输出状态代码status和消息statusMessage。     void cgiHeaderContentType(char *mimeType)    用于告知浏览器返回的是什么类型的文档。在任何向浏览器输出之前被调用,否则将出错或浏览器不能识别。     cgiEnvironmentResultType cgiWriteEnvironment(char *filename)     本函数把当前CGI环境写入filename文件中以便以后调试时使用     cgiEnvironmentResultType cgiReadEnvironment(char *filename)     本函数从filename文件中读取CGI环境以便用来调试。 5、CGI结果编码 CGIC结果编码参考: cgiFormSuccess 提交信息成功 cgiFormTruncated 删除部分字节. cgiFormBadType 错误的输入信息(没有按要求) cgiFormEmpty 提交信息为空. cgiFormNotFound 提交信息没有找到. cgiFormConstrained 数字属于某个特定的范围,被迫低于或高于适当范围。 cgiFormNoSuchChoice 单一选择提交的值是不被接受。通常说明表但和程序之间存在矛盾。 cgiEnvironmentIO 从CGI环境或获取的文件读或写的企图失败,报出I/O的错误。 cgiEnvironmentMemory 从CGI环境或获取的文件读或写的企图失败,报出out-of-memory的错误。 cgiEnvironmentSuccess 从CGI环境或获取的文件读或写的企图成功。 6、CGI环境变量 REQUEST_METHOD    请求类型,如“GET”或“POST” CONTENT_TYPE    被发送数据的类型 CONTENT_LENGTH    客户端向标准输入设备发送的数据长度,单位为字节 QUERY_STRING    查询参数,如“id=10010&sn=liigo” SCRIPT_NAMECGI    脚本程序名称 PATH_INFOCGI    脚本程序附加路径 PATH_TRANSLATEDPATH_INFO    对应的绝对路径 REMOTE_ADDR    发送此次请求的主机IP REMOTE_HOST    发送此次请求的主机名 REMOTE_USER    已被验证合法的用户名 REMOTE_IDENTWEB    服务器的登录用户名 AUTH_TYPE    验证类型 GATEWAY_INTERFACE    服务器遵守的CGI版本,如:CGI/1.1 SERVER_NAME    服务器主机名、域名或IP SERVER_PORT    服务器端口号 SERVER_PROTOCOL    服务器协议,如:HTTP/1.1 DOCUMENT_ROOT    文档根目录 SERVER_SOFTWARE    服务器软件的描述文本 HTTP_ACCEPT    客户端可以接收的MIME类型,以逗号分隔 HTTP_USER_AGENT    发送此次请求的web浏览器 HTTP_REFERER    调用此脚本程序的文档 HTTP_COOKIE    获取COOKIE键值对,多项之间以分号分隔,如:key1=value1;key2=value2

  • 2019-02-25
  • 发表了主题帖: 芯灵思SinlinxA33开发板 Linux平台总线设备驱动

    1、什么是platform(平台)总线?相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。那为什么需要platform总线呢?其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与我们的cpu进行数据交互的,但是在我们的嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。platform总线相关代码:driver\base\platform.c 文件 相关结构体定义:include\linux\platform_device.h 文件中 2、platform总线管理下的2员大将(1)两个结构体platform_device和platform_driver对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体在platform总线下就是platform_device和platform_driver,下面是对两个结构体的各个元素进行分析:platform_device结构体:(include\linux\platform_device.h) struct platform_device {           //  platform总线设备     const char    * name;          //  平台设备的名字     int        id;                 //   ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)     struct device    dev;          //   内置的device结构体     u32        num_resources;      //   资源结构体数量     struct resource    * resource; //   指向一个资源结构体数组     const struct platform_device_id    *id_entry; //  用来进行与设备驱动匹配用的id_table表     /* arch specific additions */     struct pdev_archdata    archdata;             //  自留地    添加自己的东西 }; platform_device结构体中的struct resource结构体分析: struct resource {      // 资源结构体     resource_size_t start;      // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址     resource_size_t end;        // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址     const char *name;           // 资源名     unsigned long flags;        // 资源的标示,用来识别不同的资源     struct resource *parent, *sibling, *child;   // 资源指针,可以构成链表 }; platform_driver结构体:(include\linux\platform_device.h) struct platform_driver {     int (*probe)(struct platform_device *);     //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个     int (*remove)(struct platform_device *);    //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了     void (*shutdown)(struct platform_device *);     int (*suspend)(struct platform_device *, pm_message_t state);     int (*resume)(struct platform_device *);     struct device_driver driver;                //   内置的device_driver 结构体     const struct platform_device_id *id_table;  //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组 }; (2)两组接口函数(driver\base\platform.c)int platform_driver_register(struct platform_driver *);       // 用来注册我们的设备驱动   void platform_driver_unregister(struct platform_driver *);  // 用来卸载我们的设备驱动int platform_device_register(struct platform_device *);      // 用来注册我们的设备      void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备 3、platform平台总线的初始化(1)platform平台总线的注册初始化:  platform_bus_init/***********************************************************************/platform_bus_init    early_platform_cleanup               //  进行一些早期的平台清理       device_register                          //  注册设备 (在/sys/devices/目录下建立 platform目录对应的设备对象  /sys/devices/platform/)    bus_register                              //  总线注册/************************************************************************/ (2)相关结构体struct bus_type {    const char        *name;                     //  总线名字    struct bus_attribute    *bus_attrs;          //  该总线的属性    struct device_attribute    *dev_attrs;       //  该总线下设备的属性    struct driver_attribute    *drv_attrs;       //  该总线下设备驱动的属性     int (*match)(struct device *dev, struct device_driver *drv);     //  该总线下设备与设备驱动的匹配函数    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);  //   事件函数    热拨插    int (*probe)(struct device *dev);                                //   总线下的  探针函数    int (*remove)(struct device *dev);    void (*shutdown)(struct device *dev);     int (*suspend)(struct device *dev, pm_message_t state);    int (*resume)(struct device *dev);     const struct dev_pm_ops *pm;  //  电源管理相关的     struct bus_type_private *p;   //  总线的私有数据  p->subsys.kobj 表示该总线在驱动模型中对应的对象};struct bus_type_private {    struct kset subsys;                //  这个是bus主要的kset    struct kset *drivers_kset;         //  这个kset指针用来指向该总线的 drivers目录的    struct kset *devices_kset;         //  这个kse指针用来指向该总线的devices目录的    struct klist klist_devices;        //  用来挂接该总线下的设备的一个链表头    struct klist klist_drivers;        //   用来挂接该总线下的设备驱动的一个链表头    struct blocking_notifier_head bus_notifier;    unsigned int drivers_autoprobe:1;  //   是否需要在设备驱动注册时候子自动匹配设备    struct bus_type *bus;              //  指向本bus结构体};(3)函数详解bus_register:int platform_device_add(struct platform_device *pdev){    int i, ret = 0;     if (!pdev)        return -EINVAL;     if (!pdev->dev.parent)                      pdev->dev.parent = &platform_bus;       //  将平台设备的父设备设置为 platform_bus (对应的就是  /sys/devices/platform 这个目录)     pdev->dev.bus = &platform_bus_type;         //  设置平台设备挂接在 platform总线下     platform_bus_type     if (pdev->id != -1)        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id); //  给平台设备对应的对象设置名字  name.id  (如果我们的 pdev->id 设置不等于-1时)    else        dev_set_name(&pdev->dev, "%s", pdev->name); //   下面的for 循环是对平台设备资源的一些处理    for (i = 0; i < pdev->num_resources; i++) {        struct resource *p, *r = &pdev->resource;         if (r->name == NULL)            r->name = dev_name(&pdev->dev);         p = r->parent;        if (!p) {            if (resource_type(r) == IORESOURCE_MEM)                p = &iomem_resource;            else if (resource_type(r) == IORESOURCE_IO)                p = &ioport_resource;        }         if (p && insert_resource(p, r)) {            printk(KERN_ERR                   "%s: failed to claim resource %d\n",                   dev_name(&pdev->dev), i);            ret = -EBUSY;            goto failed;        }    }//////////////////////////////////////////////////////////////////     pr_debug("Registering platform device '%s'. Parent at %s\n",         dev_name(&pdev->dev), dev_name(pdev->dev.parent));     ret = device_add(&pdev->dev);    //   将平台设备添加到系统中去  /sys/devices/platform/xxx    if (ret == 0)        return ret; failed:    while (--i >= 0) {        struct resource *r = &pdev->resource;        unsigned long type = resource_type(r);         if (type == IORESOURCE_MEM || type == IORESOURCE_IO)            release_resource(r);    }     return ret;}5、platform平台设备驱动注册(1)platform平台设备驱动注册函数: platform_driver_register/*********************************************************************/platform_driver_register    driver_register        driver_find        bus_add_driver            kobject_init_and_add            driver_attach            klist_add_tail            module_add_driver            driver_create_file            driver_add_attrs        driver_add_groups/************************************************************/(2)函数详解platform_driver_register:int platform_driver_register(struct platform_driver *drv){    drv->driver.bus = &platform_bus_type;            //  设置设备驱动 挂接在 platform平台总线下   //  下面做的就是对 drv 中的函数指针进行填充    if (drv->probe)        drv->driver.probe = platform_drv_probe;      if (drv->remove)        drv->driver.remove = platform_drv_remove;    if (drv->shutdown)        drv->driver.shutdown = platform_drv_shutdown;     return driver_register(&drv->driver);             //  注册设备驱动}int driver_register(struct device_driver *drv){    int ret;    struct device_driver *other;           //    定义一个设备驱动指针  other     BUG_ON(!drv->bus->p);     if ((drv->bus->probe && drv->probe) ||        (drv->bus->remove && drv->remove) ||        (drv->bus->shutdown && drv->shutdown))        printk(KERN_WARNING "Driver '%s' needs updating - please use "            "bus_type methods\n", drv->name);     other = driver_find(drv->name, drv->bus);  //   这个函数其实进行了一个校验  比对当前的 总线下是否存在名字和现在需要注册的设备驱动的名字相同的设备驱动    if (other) {        put_driver(other);                     //   如果名字相同 直接打印错误  并退出        printk(KERN_ERR "Error: Driver '%s' is already registered, "            "aborting...\n", drv->name);        return -EBUSY;    }     ret = bus_add_driver(drv);                  //   在总线挂接设备驱动  就是将设备驱动对应的kobj对象与组织建立关系    if (ret)        return ret;    ret = driver_add_groups(drv, drv->groups);   //      if (ret)        bus_remove_driver(drv);    return ret;}bus_add_driver:int bus_add_driver(struct device_driver *drv){    struct bus_type *bus;             //  定义一个bus_type 结构体指针    struct driver_private *priv;      //   定义一个 driver_private  指针    int error = 0;     bus = bus_get(drv->bus);       //   获取 drv的bus    if (!bus)        return -EINVAL;     pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);     priv = kzalloc(sizeof(*priv), GFP_KERNEL);  //  给priv 申请分配内存空间    if (!priv) {        error = -ENOMEM;        goto out_put_bus;    }    klist_init(&priv->klist_devices, NULL, NULL);  //  初始化 priv->klist_devices 链表    priv->driver = drv;                            //  使用 priv->driver  指向 drv    drv->p = priv;                                 //   使用drv->p 指向 priv    这两步见多了  ,跟之前分析的是一样的意思  就是建立关系    priv->kobj.kset = bus->p->drivers_kset;        //   设置设备驱动对象的父对象(  也就是指向一个 kset )    父对象就是   /sys/bus/bus_type/drivers/  这个目录对应的对象    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, //  添加kobject 对象到目录层次中     就能够在  /sys/bus/bus_type/drivers/ 目录中看到设备驱动对应的文件了                     "%s", drv->name);                             //  priv->kobj->ktype =  driver_ktype     对象类型    if (error)        goto out_unregister;     if (drv->bus->p->drivers_autoprobe) {       //  如果定义了自动匹配设备标志位    则在线下面进行自动匹配        error = driver_attach(drv);             //  尝试将驱动绑定到设备 也就是通过这个函数进行设备与设备驱动的匹配        if (error)            goto out_unregister;    }    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);  //  链表挂接:   priv->knode_bus  挂接到  bus->p->klist_drivers 链表头上去    module_add_driver(drv->owner, drv);     error = driver_create_file(drv, &driver_attr_uevent);   //  建立属性文件:   uevent    if (error) {        printk(KERN_ERR "%s: uevent attr (%s) failed\n",            __func__, drv->name);    }    error = driver_add_attrs(bus, drv);                    //  根据总线的   bus->drv_attrs  来建立属性文件    if (error) {        /* How the hell do we get out of this pickle? Give up */        printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",            __func__, drv->name);    }     if (!drv->suppress_bind_attrs) {        error = add_bind_files(drv);        if (error) {            /* Ditto */            printk(KERN_ERR "%s: add_bind_files(%s) failed\n",                __func__, drv->name);        }    }     kobject_uevent(&priv->kobj, KOBJ_ADD);    return 0; out_unregister:    kobject_put(&priv->kobj);    kfree(drv->p);    drv->p = NULL;out_put_bus:    bus_put(bus);    return error;} driver_attach: 上面说到了当注册platform平台设备驱动时会进行自动匹配的原理,那么当我们注册platform平台设备时进行自动匹配的代码在哪里呢?其实这个之前在分析device_create函数时就已经分析过了,只不过没有去详细的分析:/**********************************************/platform_device_add    device_add        bus_probe_device     //  关键就在这个函数/*********************************************/函数分析: int driver_register(struct device_driver *drv){    int ret;    struct device_driver *other;           //    定义一个设备驱动指针  other     BUG_ON(!drv->bus->p);     if ((drv->bus->probe && drv->probe) ||        (drv->bus->remove && drv->remove) ||        (drv->bus->shutdown && drv->shutdown))        printk(KERN_WARNING "Driver '%s' needs updating - please use "            "bus_type methods\n", drv->name);     other = driver_find(drv->name, drv->bus);  //   这个函数其实进行了一个校验  比对当前的 总线下是否存在名字和现在需要注册的设备驱动的名字相同的设备驱动    if (other) {        put_driver(other);                     //   如果名字相同 直接打印错误  并退出        printk(KERN_ERR "Error: Driver '%s' is already registered, "            "aborting...\n", drv->name);        return -EBUSY;    }     ret = bus_add_driver(drv);                  //   在总线挂接设备驱动  就是将设备驱动对应的kobj对象与组织建立关系    if (ret)        return ret;    ret = driver_add_groups(drv, drv->groups);   //      if (ret)        bus_remove_driver(drv);    return ret;} 总结:  所以由此可知,当我们不管是先注册设备还是先注册设备驱动都会进行一次设备与设备驱动的匹配过程,匹配成功之后就会调用probe函数,匹配的原理就是去遍历总线下的相应的链表来找到挂接在他下面的设备或者设备驱动,所以由此可以看出来,这个东西的设计其实是很美的。 6、platform总线下的匹配函数(1)platform_match函数static int platform_match(struct device *dev, struct device_driver *drv) // 总线下的设备与设备驱动的匹配函数{    struct platform_device *pdev = to_platform_device(dev);      //  通过device  变量获取到 platform_device    struct platform_driver *pdrv = to_platform_driver(drv);      //   通过 driver 获取  platform_driver     /* match against the id table first */    if (pdrv->id_table)    //   如果pdrv中的id_table 表存在        return platform_match_id(pdrv->id_table, pdev) != NULL;  //  匹配id_table    /* fall-back to driver name match */   //  第二个就是指直接匹配 pdev->name     drv->name  名字是否形同    return (strcmp(pdev->name, drv->name) == 0);} static const struct platform_device_id *platform_match_id(            const struct platform_device_id *id,            struct platform_device *pdev){    while (id->name[0]) {  //  循环去比较id_table数组中的各个id名字是否与pdev->name 相同        if (strcmp(pdev->name, id->name) == 0) {            pdev->id_entry = id;       // 将id_table数组中的名字匹配上的 这个数组项 指针赋值给 pdev->id_entry            return id;         //  返回这个指针        }        id++;    }    return NULL;}总结: 由上面可知platform总线下设备与设备驱动的匹配原理就是通过名字进行匹配的,先去匹配platform_driver中的id_table表中的各个名字与platform_device->name名字是否相同,如果相同表示匹配成功直接返回,否则直接匹配platform_driver->name与platform_driver->name是否相同,相同则匹配成功,否则失败 参考来源:https://www.cnblogs.com/deng-tao/p/6026373.html

  • 2019-02-22
  • 发表了主题帖: 芯灵思SinlinxA33开发板的安卓控制LED-2-JNI基础

    虽然您可以完全使用Java编写应用程序,但有些情况下Java本身并不能满足您的应用程序的需求。当应用程序不能完全用Java编写时,程序员使用JNI编写Java本机方法来处理这些情况。 以下示例说明何时需要使用Java本机方法: 标准Java类库不支持应用程序所需的与平台相关的功能。您已经有一个用另一种语言编写的库,并希望通过JNI使其可以访问Java代码。您希望在较低级别的语言(如汇编语言)中实现一小部分时间关键代码。 来源:https://docs.oracle.com/javase/1 ... ec/intro.html#wp725 了解JNI函数 JNI函数就是在native层定义的本地函数,对应于在java层使用native关键字声明的方法的。直白的说,就是在Java层声明,C/C++语言实现的。当然,这个函数并不一般,它会通过JNI某种机制与Java层的方法进行关联,使得Java层代码可以很方便的调用它。 jni函数的语法和调用规则 将java语言的数据类型转换成底层语言调用规则将java语言的方法调用转换成底层语言函数或方法 jni数据类型的转化 jni在java和C之间建立连接,因此jni首先要统一两者的数据类型Java Type Native Type Description boolean jboolean unsigned 8 bits byte jbyte signed 8 bits char jchar unsigned 16 bits short jshort signed 16 bits int jint signed 32 bits long jlong signed 64 bits float jfloat 32 bits double jdouble 64 bits void void N/A The following definition is provided for convenience.#define JNI_FALSE  0 #define JNI_TRUE   1 The jsize integer type is used to describe cardinal indices and sizes:typedef jint jsize; java中可以直接调用底层语言的函数,jni规定了java调用底层语言的方法签名 java中有重载概念,所以java方法转换成签名,签名再转化为函数Type SignatureJava TypeZ boolean B byte C char S short I int J long F float D double L fully-qualified-class ; fully-qualified-class [ type type[] ( arg-types ) ret-type method type For example, the Java method:long f (int n, String s, int[] arr); has the following type signature:(ILjava/lang/String;[I)J jni机制实现 native 声明的函数可以不实现 class Cls {      native double f(int i, String s);                 // 声明为本地方法      static {          System.loadLibrary(“库名”);         // 通过静态初始化语句块来加载动态库      } } 程序实现测试java代码 javah -jni Hello 生成Hello .h 打开Hello .h 文件里面已经写好C函数的名字 C代码 将C文件制做成共享库.so,注意指定jni.h和jni_md.h文件路径 gcc -shared -fPIC hello.c -o libhello.so -I /usr/java/jdk1.6.0_45/include/ -I /usr/java/jdk1.6.0_45/include/linux/ java Hello 运行程序实验现象

  • 回复了主题帖: 芯灵思SinlinxA33开发板的安卓控制LED-1

    muxb 发表于 2019-2-22 08:47 只看到了toast显示,没看到控制啊。
    还有后续

  • 2019-02-21
  • 发表了主题帖: 芯灵思SinlinxA33开发板的安卓控制LED-1

    打开Android Studio3.1 新建一个 NO Activity 项目 切换为project视图,到/app/src/main/java/com.xxxxxx目录,右击新建Activity->empty Activity 不要勾选黄色选项 (勾选Generate Layout File表示会自动为FirstActivity创建一个对应的布局文件,勾选Launcher Activity表示会自动将FirstActivity设置为当前项目的主活动) 右击app/src/main/res目录→New→Directory,弹出一个新建目录的窗口,根元素就默认选择为LinearLayout 点击OK完成布局的创建,这时候你会看到如图所示的布局编辑器。点击右边的Text 添加了两个Button元素。 在AndroidManifest文件中注册 在活动中使用Toast (Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间) 点击运行 运行结果 此内容由EEWORLD论坛网友babyking原创,如需转载或用于商业用途需征得作者同意并注明出处

  • 2019-02-20
  • 发表了主题帖: 芯灵思SinlinxA33开发板 Linux内核信号量学习

    在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。 自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"。但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"。鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。 一、信号量       信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:    (1) 测试控制该资源的信号量。    (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。    (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。    (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。     维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获得一个信号量ID。Linux2.6.26下定义的信号量结构体: semaphore.h        linux-3.5\include\Linux struct semaphore {         raw_spinlock_t                lock;         unsigned int                count;     //信号值,值为正数表示可以资源,0表示不能获得资源         struct list_head        wait_list; }; 初始化信号量 void sema_init (struct semaphore *sem, int val); 该函数初始化信号量,并设置信号量sem的值为val void init_MUTEX (struct semaphore *sem); 该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1,等同于sema_init (struct semaphore *sem, 1); void init_MUTEX_LOCKED (struct semaphore *sem); 该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,等同于sema_init (struct semaphore *sem, 0); 获得信号量 void down(struct semaphore * sem); 该函数用于获得信号量sem,它会导致睡眠(这个睡眠和下面所说的不知道有什么不同,既然不能被其它地方唤醒,那么这个down有什么用呢?),因此不能在中断上下文使用; int down_interruptible(struct semaphore * sem); 该函数功能与down类似,不同之处为,down不能被信号打断,但down_interruptible能被信号打断;(这个能被信号打断,有点疑惑,我现在做的项目是使用的是被中断打断,不知道它这个地方所说的是什么意思) int down_trylock(struct semaphore * sem); 该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。 释放信号量 void up(struct semaphore * sem); 该函数释放信号量sem,唤醒等待者。 2.互斥锁 2.1概念 互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。mutex实际上是count=1情况下的semaphore。 // 结构 struct mutex {         /* 1: unlocked, 0: locked, negative: locked, possible waiters */         atomic_t                  count;         spinlock_t                wait_lock;         struct list_head          wait_list; #ifdef CONFIG_DEBUG_MUTEXES         struct thread_info        *owner;         const char                *name;         void                      *magic; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC         struct lockdep_map         dep_map; #endif }; // 定义互斥锁lock mutex_init(struct mutex* lock)   或者直接用 #define DEFINE_MUTEX(LOCK)即可; // 获取 mutex_lock(struct mutex *lock) // 释放 mutex_unlock(struct mutex *lock) struct mutex lock; mutex_init(&lock);初始化互斥锁 或者直接用 #define DEFINE_MUTEX(LOCK)即可; #define __MUTEX_INITIALIZER(lockname) \         { .count = ATOMIC_INIT(1) \         , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \         , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \         __DEBUG_MUTEX_INITIALIZER(lockname) \         __DEP_MAP_MUTEX_INITIALIZER(lockname) } #define DEFINE_MUTEX(mutexname) \     struct mutex mutexname = __MUTEX_INITIALIZER(mutexname) extern void __mutex_init(struct mutex *lock, const char *name,              struct lock_class_key *key); 三:与自旋锁相关的API主要有: 定义自旋锁 spinlock_t spin; 初始化自旋锁 spin_lock_init(lock) 该宏用于动态初始化自旋锁lock 获得自旋锁 spin_lock(lock) 该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放; spin_trylock(lock) 该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再"在原地打转"; 释放自旋锁 spin_unlock(lock) 该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用; 除此之外,还有一组自旋锁使用于中断情况下的API。 https://sinlinx.taobao.com/ 参考博客:https://www.cnblogs.com/biaohc/p/6679195.html

  • 2019-02-19
  • 发表了主题帖: 芯灵思SinlinxA33开发板Linux内核原子操作(附实测代码)

    原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何线程切换。原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。但是,在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以decl (递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。设想在不同CPU运行的两个进程都在递减某个计数值,可能发生的情况是:                                ⒈ CPU A(CPU A上所运行的进程,以下同)从内存单元把当前计数值⑵装载进它的寄存器中;                                ⒉ CPU B从内存单元把当前计数值⑵装载进它的寄存器中。                               ⒊ CPU A在它的寄存器中将计数值递减为1;                               ⒋ CPU B在它的寄存器中将计数值递减为1;                               ⒌ CPU A把修改后的计数值⑴写回内存单元。                               ⒍ CPU B把修改后的计数值⑴写回内存单元。 我们看到,内存里的计数值应该是0,然而它却是1。如果该计数值是一个共享资源的引用计数,每个进程都在递减后把该值与0进行比较,从而确定是否需要释放该共享资源。这时,两个进程都去掉了对该共享资源的引用,但没有一个进程能够释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用。 Linux原子操作大部分使用汇编语言实现,因为c语言并不能实现这样的操作。 原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中 原子操作相关API atomic.h        这个文件中包含了和具体芯片架构相关的原子操作头文件arch\arm\include\asm\atomic.h。 ATOMIC_INIT(v); 初始化一个个原子变量,一般比较少用。 atomic_read(atomic_t * v); 读取原子变量中的值 atomic_set(atomic_t * v, int i); 设置原子变量值为i void atomic_add(int i, atomic_t *v) 把原子变量值加上i void atomic_sub(int i, atomic_t *v) 把原子变量值减去i atomic_sub_and_test(i, v) 把原子变量v的值减去i,判断相减后的原子变量值是否为0,如果为0返回真 atomic_inc(v); 把原子变量v加上1 atomic_dec(v) 把原子变量v减去1 atomic_dec_and_test(v) 把原子变量v的值减去1,判断相减后的原子变量值是否为0,如果为0返回真 atomic_inc_and_test(v) 把原子变量v的值加1,判断相加后的原子变量值是否为0,如果为0返回真 atomic_add_negative(i,v) 把原子变量v的值加i,判断相加后的原子变量值是否为负数,如果为负数返回真 int atomic_add_return(int i, atomic_t *v) 把原子变量v的值加i,返回相加后原子变量的结果 int atomic_sub_return(int i, atomic_t *v) 把原子变量v的值减i,返回相减后原子变量的结果 atomic_inc_return(v) 把原子变量v的值加1后,返回结果 atomic_dec_return(v) 把原子变量v的值减1后返回结果 实验现象:当多个APP调用同一个驱动时,不会发生混乱,依次执行#  ./ledtest & ./ledtest&  ./ledtest & 未实现原子操作会都执行 驱动代码: #include #include #include #include #include #include #include #include #include #include #include #include #include static int major; static struct class *led_class; volatile unsigned long *gpio_con = NULL; volatile unsigned long *gpio_dat = NULL; //定义原子变量  ,初始化值为1 atomic_t  atomic_v = ATOMIC_INIT(1); static int led_open (struct inode *node, struct file *filp) {     // atomic_dec_and_test(v),判断减1结果是否0,为0返回真。     if( !atomic_dec_and_test(&atomic_v) ){           printk("done done done \n");                 return -1;         }         /* PB7 - 0x01C20824 */         if (gpio_con) {                 printk("ioremap  0x%x\n", gpio_con);         }         else {                 return -EINVAL;         }         printk(" open open open  \n");         return 0; } static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off) {         unsigned char val;                 copy_from_user(&val, buf, 1);         if (val)         {                 *gpio_dat |= (1

  • 2019-02-18
  • 发表了主题帖: 芯灵思SinlinxA33开发板Linux内核workqueue(附实测代码)

    内核工作队列概述 工作队列(workqueue)是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行,最重要的就是工作队列允许被重新调度甚至睡眠。 linux workqueue工作原理 linux系统启动期间会创建名为kworker/u:x(x是0开始的整数,表示CPU编号)工作者内核线程,该线程创建之后处于sleep状态。从调度器的角度来解释,内核线程就是可以调度的进程;从代码表现形式看,本质是一个函数。 工作队列结构原理 work_struct,workqueue_struct,struct cpu_workqueue_struct三者之间关系如下: 内核启动时会为每一个CPU创建一个cpu_workqueue_struct结构,同时还会有一个内核工作的线程,这个线程创建好后处于睡眠状态,等待用户加入工作,来唤醒线程去调度工作结构体 work_struct 中的工作函数。工作work_struct 是通过链表连接在cpu_workqueue_struct上,后面其他work_struct连接在前一个后面,组成一个队列工作者线程被唤醒后,会去自己负责的工作队列上依次执行上面struct_work结构中的工作函数,执行完成后就会把 work_struct 从链表上删除。如果想使用工作队列来延后执行一段代码,必须先创建work_struct -> cpu_workqueue_struct,然后把工作节点work_struct加入到workqueue_struct工作队列中,加入后工作者线程就会被唤醒,在适当的时机就会执行工作函数。 工作队列数据结构 work_struct 我们把推后执行的任务叫工作(work),描述它的数据结构为work_struct 路径:workqueue.h /work/lichee/linux-3.4/include/linux/workqueue.h struct work_struct {     atomic_long_t data;     struct list_head entry;  //链表指针 把每个工作连接在一个链表上组成一个双向链表     work_func_t func;        //函数指针  指向工作函数 #ifdef CONFIG_LOCKDEP     struct lockdep_map lockdep_map; #endif }; 补充work_func_t结构             typedef void (*work_func_t)(void *work); 补充list_head结构 struct list_head {     struct list_head *next, *prev; }; 我们编程只需要关注func成员它是工作函数指针就是用户需要延后执行的代码 workqueue_struct 这个结构是用来描述内核队列的数据结构。定义在workqueue.c 具体定义如下: struct workqueue_struct {     unsigned int        flags;      /* W: WQ_* flags */     union {         struct cpu_workqueue_struct __percpu    *pcpu;         struct cpu_workqueue_struct     *single;         unsigned long               v;     } cpu_wq;               /* I: cwq's */     struct list_head    list;       /* W: list of all workqueues */     struct mutex        flush_mutex;    /* protects wq flushing */     int         work_color; /* F: current work color */     int         flush_color;    /* F: current flush color */     atomic_t        nr_cwqs_to_flush; /* flush in progress */     struct wq_flusher   *first_flusher; /* F: first flusher */     struct list_head    flusher_queue;  /* F: flush waiters */     struct list_head    flusher_overflow; /* F: flush overflow list */     mayday_mask_t       mayday_mask;    /* cpus requesting rescue */     struct worker       *rescuer;   /* I: rescue worker */     int         nr_drainers;    /* W: drain in progress */     int         saved_max_active; /* W: saved cwq max_active */ #ifdef CONFIG_LOCKDEP     struct lockdep_map  lockdep_map; #endif     char            name[];     /* I: workqueue name */ }; 注意:这个结构表示一个工作队列,一般情况下驱动开发者不需要接触太多这个结构成员,关于队列操作,内核都提供了相应的API函数 cpu_workqueue_struct struct cpu_workqueue_struct {     struct global_cwq   *gcwq;      /* I: the associated gcwq */     struct workqueue_struct *wq;        /* I: the owning workqueue */     int         work_color; /* L: current color */     int         flush_color;    /* L: flushing color */     int         nr_in_flight[WORK_NR_COLORS];                         /* L: nr of in_flight works */     int         nr_active;  /* L: nr of active works */     int         max_active; /* L: max active works */     struct list_head    delayed_works;  /* L: delayed works */ }; 内核通过delayed_works成员把第一个 work_struct 连接起来,后面work_struct通过本身的entry成员把自己连接在链表上。 内核工作队列分类 内核工作队列分成共享工作队列和自定义工作队列两种 共享工作队列 系统在启动时候自动创建一个工作队列驱动开发者如果想使用这个队列,则不需要自己创建工作队列,只需要把自己的work添加到这个工作队列上即可。 使用schedule_work这个函数可以把work_struct添加到工作队列中 自定义工作队列 由于共享工作队列是大家共同使用的,如果上面的工作函数有存在睡眠的情况,阻塞了,则会影响到后面挂接上去的工作执行时间,当你的动作需要尽快执行,不想受其它工作函数的影响,则自己创建一个工作队列,然后把自己的工作添加到这个自定义工作队列上去。 使用自定义工作队列分为两步: 创建工作队列:使用creat_workqueue(name)创建一个名为name的工作队列 把工作添加到上面创建的工作队列上:使用queue_work函数把一个工作结构work_struc添加到指定的工作队列上 linux内核共享工作队列 共享工作队列介绍 内核为了方便驱动开发者使用工作队列,给我们创建好一个工作队列,只要使用schedule_work 添加的工作节点都是添加到内核共享工作队列中,使用方法只需要开发者实现一个 work_struct结构,然后把它添加到共享工作中去。 内核共享队列API 静态定义工作结构DECLARE_WORK #define DECLARE_WORK(n, f)                  \     struct work_struct n = __WORK_INITIALIZER(n, f) 功能:定义一个名字为n的work_struct结构变量,并且初始化它,工作是f。 参数:n要定义的work_struct结构变量名,f工作函数,要延后执行的代码 动态初始化工作结构INIT_WORK #define INIT_WORK(_work, _func)                 \     do {                            \         __INIT_WORK((_work), (_func), 0);       \     } while (0) 功能:运行期间动态初始化work_struct结构 参数:_work要定义的work_struct结构变量地址,_func工作函数,要延后执行的代码 调度工作schedule_work 声明路径在workqueue.h workqueue.h /work/lichee/linux-3.4/include/linux/workqueue.h int queue_work(struct workqueue_struct *wq, struct work_struct *work) {     int ret;     ret = queue_work_on(get_cpu(), wq, work);     put_cpu();     return ret; } 功能:把一个work_struct添加道共享工作队列中,成为一个工作节点。 参数:work要定义的work_struct结构变量名地址 返回值:0 表示已经挂接到共享工作队列上还未执行。非0 其他情况内核未做说明 函数返回值通常不需要驱动开发者关注 共享队列的使用步骤 需要工作队列 创建工作 调度工作(创建工作节点) 对于共享工作队列来讲,第一部已经有了,需要做第2/3步#include #include //添加头文件 #include //实现一个work_func工作函数 void mywork_func(struct work_struct *work) {   printk("%s is call!!  work:%p\r\n",__FUNCTION__,work); } //定义一个struct work_struct结构变量,并且进行初始化 DECLARE_WORK(mywork,mywork_func); //定义并且初始化 static int __init mywork_init(void) {   //一安装模块就进行调度   schedule_work(&mywork);   printk("%s is call!!",__FUNCTION__);     return 0; } static void __exit mywork_exit(void) {   printk("mywork is exit!\r\n"); } module_init(mywork_init); module_exit(mywork_exit); MODULE_LICENSE("GPL"); 复制代码 参考博文:https://blog.csdn.net/z961968549/article/details/78758527

  • 2019-02-15
  • 发表了主题帖: 芯灵思SinlinxA33开发板Linux内核 tasklet 机制(附实测代码)

    Linux 中断编程分为中断顶半部,中断底半部 中断顶半部: 做紧急,耗时短的事情,同时还启动中断底半部。 中断底半部: 做耗时的事件,这个事件在执行过程可以被中断。 中断底半部实现方法: tasklet,工作队列,软中断等机制实现。实际上是把耗时事件推后执行,不在中断程序执行。 什么是tasklet? Tasklet 一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。这个 tasklet 绑定的函数在一个时刻只能在一个 CPU 上运行  ,tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。一个使用 tasklet 的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用 tasklet 使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。 tasklet 机制核心数据结构 tasklet 机制核心数据结构 Interrupt.h linux-3.5\include\Linux struct tasklet_struct {       struct tasklet_struct *next; // tasklet_struct 结构链表       unsigned long state; //当前这个 tasklet 是否已经被调度       atomic_t count;       void (*func)(unsigned long); //指向 tasklet 绑定的函数的指针       unsigned long data; //传递给tasklet 绑定的函数的参数 };复制代码 tasklet 相关 API 初始化相关 1. 静态初始化 DECLARE_TASKLET(name, func, data) 作用:定义一个名字为 name 的 tasklet_struct 结构变量,并且初始化这个结构。 所定义的这个 tasklet 是可以被调度,默认是处于被使能状态。 2. 静态初始化 DECLARE_TASKLET_DISABLED(name, func, data) 作用:定义一个名字为 name 的 tasklet_struct 结构变量,并且初始化这个结构。所定义的这个 tasklet 是不能被调度,默认是处于被禁能状态。 要调度这个 tasklet 需要先使能。 3.动态初始化 void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data) 作用:初始化一个 tasklet_struct 结构变量,初始化的结构默认是处于激活状态,可以被调度。 tasklet使能函数 1. void tasklet_disable(struct tasklet_struct *t) 作用:函数激活给定的 tasklet被 tasklet_schedule 调度 2. void tasklet_enable (struct tasklet_struct *t) 作用:函数禁止给定的 tasklet被 tasklet_schedule 调度 tasklet 调度函数   void tasklet_schedule (struct tasklet_struct *t) 作用:调用 tasklet_schedule 函数去通知内核帮我们调度所绑定的函数 void tasklet_kill(struct tasklet_struct *t); 作用:取消调度函数 编程步骤 Step1  定义并静态初始化tasklet_struct 结构变量 Step2  编写tasklet服务函数 Step3  在适当的地地方进行调度 Step4  在适当的地地方取消调度 实验平台:芯灵思SINA33开发板 嵌入式linux 开发板交流 QQ:641395230 驱动代码: #include #include #include void tasklet_fun(unsigned long data); //Step1 定义并静态初始化tasklet_struct 结构变量 DECLARE_TASKLET(mytasklet, tasklet_fun, 651); //Step2 tasklet服务函数 void tasklet_fun(unsigned long data) {         static unsigned long count = 0;         printk("count:%lu,%s is call! data:%lu\r\n",count++,__FUNCTION__,data);         tasklet_schedule(&mytasklet); //在工作函数中重新调度自己,这样会循环调用tasklet_fun } static int __init mytasklet_init(void) {         //Step3 开始调度 mytasklet         tasklet_schedule(&mytasklet);         printk("%s is call!\r\n",__FUNCTION__);         return 0; } static void __exit mytasklet_exit(void) //Module exit function specified by module_exit() {         //Step4 删除 tasklet         tasklet_kill(&mytasklet); } module_init(mytasklet_init); module_exit(mytasklet_exit); MODULE_LICENSE("GPL"); 复制代码 实验现象

  • 2019-02-13
  • 发表了主题帖: 芯灵思SinlinxA33开发板Linux中断编程4-最终代码(1)

    按键驱动代码: #include #include #include #include #include #include #include #include #include    #define DEVICE_BUTTON  "mybtn" #define BTN_MAJOR   255 static char keybuf[] = {"0"}; static struct miscdevice misc = {     .minor = BTN_MAJOR,     .name  = DEVICE_BUTTON, }; irqreturn_t key_isr(int irq, void* dev) {     int btn = 0;         //读取按键状态     btn= !gpio_get_value(GPIOL(14));            printk("key %s\r\n",  btn ? "down" : "up");         //把按键状态更新到对应的按缓冲中         keybuf = “0” + btn;     return IRQ_HANDLED; } static ssize_t btn_read  (struct file *flp, char __user *buff, size_t count, loff_t * off) {     int ret ;         if(!count) {          return 0;     }     ret = copy_to_user(buff, keybuf, count);     if(ret) {         printk("error:copy_to_user\r\n");         return -EFAULT;     }     return count; } static const struct file_operations dev_fops = {     .read   =   btn_read ,     .owner  =   THIS_MODULE, }; static int __init btn_init(void) {     int ret;     int irq;     int flags;     flags = IRQ_TYPE_EDGE_BOTH;     irq = gpio_to_irq( GPIOL(14) );         //发生中断号为irq的中断会执行key_isr函数。注册成功会在/proc/irq号/KEY文件夹出现或 cat /proc/interrupts, 记录了本中断发生的次数cat /proc/irq/442/spurious     ret = request_irq(irq,key_isr, flags, "KEY",NULL);     if(ret < 0)     {               irq = gpio_to_irq( GPIOL(14) );            disable_irq(irq);            free_irq(irq, NULL);            return ret;     }     ret = misc_register(&misc);     printk(KERN_EMERG " Device registered \n ");     return ret; } static void __exit btn_exit(void) {     int irq;     irq = gpio_to_irq( GPIOL(14) );     disable_irq(irq);     free_irq(irq, NULL);     misc_deregister(&misc);     printk(KERN_EMERG " Equipment logged out \n"); } module_init(btn_init); module_exit(btn_exit); MODULE_LICENSE("GPL"); 复制代码 测试应用代码: #include #include #include #include #include #include #include #include #include #include     #define  DEV_NAME    "/dev/mybtn" int main(int argc, char *args[]) {     int fd = 0;     int ret = 0;     unsigned char recv_buf[1] = {"0"};     fd = open(DEV_NAME, O_RDONLY);     //fd = open(DEV_NAME, O_RDONLY|O_NONBLOCK);     if(fd < 0) {         perror("open");     }     while(1) {         strcpy(recv_buf, "0000");         //读取按键数据         ret = read(fd, recv_buf, 1);         if((ret < 0) && (errno != EAGAIN)) {             perror("read");             exit(-1);         }         //输出按键状态         printf("%s\r\n", recv_buf);     }     return 0; } 复制代码 Makefile代码: KERN_DIR = /work/lichee/linux-3.4 all:         make -C $(KERN_DIR) M=`pwd` modules         arm-none-linux-gnueabi-gcc  btntest.c -o btntest clean:         make -C $(KERN_DIR) M=`pwd` modules clean         rm -rf modules.order obj-m        += btn_drv.o 复制代码 遗憾的是,代码虽然能编译成功,但是驱动加载有错误,等找出原因再发帖补充,同时也希望大神指点 开发板交流群 QQ:641395230 此内容由EEWORLD论坛网友babyking原创,如需转载或用于商业用途需征得作者同意并注明出处

  • 2019-02-12
  • 发表了主题帖: 芯灵思Sinlinx A33开发板 Linux内核等待队列poll ---阻塞与非阻塞

    阻塞与非阻塞的概念 阻塞:阻塞调用是指调用结果返回之前,当前进程程会被挂起(休眠)。函数只有在得到结果之后才会返回。默认情况下,文件都是以这种方式打开。 非阻塞:指在不能立刻得到结果之前,该函数不会阻塞当前进程程,而会立刻返回。应用程序可选择以阻塞或非阻塞方式打开设备文件,然后设备进行读写操作,如果驱动的读写函数支持阻塞和非阻塞功能,这两种打开方式才会有区别。 阻塞示例 :fd = open("/xxx/word", O_RDONLY ); // 默认阻塞方式打开         如果此时没有数据可以读取,则执行休眠          如果有数据可以读取,则马上读取数据,不休眠,读取数据后马上返回。 非阻塞示例 :fd = open("/xxx/word", O_RDONLY | O_NONBLOCK ); //非阻塞方式打开         如果此时已经有数据可以读取,则读取数据再返回。         如果没有数据可以读,也马上返回,但是返回一个错误码。 1)驱动中如何得到用户空间应用程序打开的方式?                 open一个设备,内核会创建一个file结构,并且把打开方式的数值存放到file结构成员f_flags成员中,驱动程序的read,write 接口可以使用参数file指针取得文件打开方式。file结构中有一个成员是f_flags ,创建时候,内核会把open 函数的最后一个参数 flag 数值保存在 f_flags 变量中。 static ssize_t xxx_read(struct file *pfile, char user *buf, size_t count, loff_t *poff) {         ……         //判断当前是否有按键动作         if(没有按键动作)     {                 //判断 pfile->f_flags 成员是否设置 O_NONBLOCK                   if(pfile->f_flags & O_NONBLOCK) //表示用户空间使用非阻塞打开                 {                         return - EAGAIN; //返回一个错误码,告诉用户空间你可以再尝试读取                 }                 //阻塞方式打开,没有数据就休眠,不马上返回else                 {                         //休眠,等待有按键动作唤醒进程。                 }         } } 2)如何知道是否有按键动作?         如果按键按键或松开时刻,会产生一个中断,所以,在中断程序设置一个标志即可。         定义一个全局变量,初始值为 0,表示没有按键动作发生,在中断程序中设置这个变量值为 1,表示发生按键动作。 3)如何让进程进入休眠状态?         最简单,最直接的休眠方式: msleep 函数         这个函数:一旦调用,则调用进程会休眠指定长的时间,时间一到内核会唤醒这个进程.         //休眠,等待有按键动作唤醒进程。     while(press == 0)                  msleep(5); // 休眠5ms

  • 2019-02-11
  • 发表了主题帖: 芯灵思Sinlinx A33开发板 Linux中断编程 3--- 应用程序

    应用程序代码参考 #include #include #include #include #include #include #include #include #include #include     #define  DEV_NAME    "/dev/mybtn" int main(int argc, char *args[]) {     int fd = 0;     int ret = 0;     unsigned char recv_buf[1] = {"0"};     fd = open(DEV_NAME, O_RDONLY);     //fd = open(DEV_NAME, O_RDONLY|O_NONBLOCK);     if(fd < 0) {         perror("open");     }     while(1) {         strcpy(recv_buf, "0000");         //读取按键数据         ret = read(fd, recv_buf, 1);         if((ret < 0) && (errno != EAGAIN)) {             perror("read");             exit(-1);         }         //输出按键状态         printf("%s\r\n", recv_buf);     }     return 0; }

  • 2019-02-01
  • 发表了主题帖: 芯灵思Sinlinx A33开发板 Linux中断编程 2--- 程序框架

    根据上一个帖子的分析,想要实现按键中断,首先得知道引脚对应的中断号,LRADC0对应的中断编号#define SUNXI_IRQ_LRADC                   (SUNXI_GIC_START + 30)  /* 62*/ 通过static inline int gpio_to_irq(SUNXI_IRQ_LRADC)这个函数确定这个IO上的外部中断编号 程序流程,当引脚电平发生变化触发中断,进入中断函数把按键状态更新,此时在应用程序读相应设备,则可以知道引脚状态 驱动程序框架 #define BTN_MAJOR   255 static struct miscdevice misc = {     .minor = BTN_MAJOR, //次设备号     .name  = DEVICE_BUTTON,//设备名     .fops  = &dev_fops,  //文件操作方法 }; //按键标志,'0'表示没有按键,'1'表示按下了 static char keybuf[1] = {‘0’}; //中断处理函数声明 irqreturn_t  key_isr(int irq, void* dev); //读函数,读取引脚状态 static ssize_t btn_read (struct file *flp, char __user *buff, size_t count, loff_t * off); //file_operations结构体 static const struct file_operations dev_fops = {     .read   =   btn_read ,     .owner  =   THIS_MODULE, }; //初始化函数 static int __init btn_init(void); //注销函数 static void __exit btn_exit(void); 首先先看初始化函数static int __init btn_init(void); static int __init btn_init(void) {     int ret;     int irq;//中断号     int flags; //触发标志     flags = IRQ_TYPE_EDGE_BOTH; //设置为双边触发      //得到中断号      irq = gpio_to_irq(SUNXI_IRQ_LRADC);      //注册中断      ret = request_irq(irq,key_isr, flags, “key”, NULL);      if(ret < 0)          break;     //如果不是成功,注销已经注册的中断     if(ret < 0) {               irq = gpio_to_irq(SUNXI_IRQ_LRADC);            disable_irq(irq);            free_irq(irq, NULL);            return ret;     }     //注册杂项设备     ret = misc_register(&misc);     printk(KERN_EMERG " Device registered \n ");     return ret; } 注销函数 static void __exit btn_exit(void); static void __exit btn_exit(void) {     int irq;     //注销中断     irq = gpio_to_irq(SUNXI_IRQ_LRADC);     disable_irq(irq);     free_irq(irq, NULL);     //注销杂项设备     misc_deregister(&misc);     printk(KERN_EMERG " Equipment logged out \n"); } 读函数,读取引脚状态  static ssize_t btn_read (struct file *flp, char __user *buff, size_t count, loff_t * off); static ssize_t tiny4412_read  (struct file *flp, char __user *buff, size_t count, loff_t * off) {     int ret ;     //count为0,直接返回     if(!count) {          return 0;     }     //复制数据到用户空间     ret = copy_to_user(buff, keybuf, count);     if(ret) {         printk("error:copy_to_user\r\n");         return -EFAULT;     }     return count; } 中断服务函数irqreturn_t key_isr1(int irq, void* dev) irqreturn_t key_isr1(int irq, void* dev) {     //存放按键状态     int btn = 0;     btn= !gpio_get_value(SUNXI_IRQ_LRADC );     //把按键状态更新到对应的按缓冲中     keybuf[0] = dn + '0';     //输出按键提示     printk("key %s\r\n",  dn ? "down" : "up");     return IRQ_HANDLED;//目前只是实现一个按键的,这个是共享中断的情况才用到, 在中断到来时,会遍历共享此中断的所有中断处理程序, 直到某一个中断服务函数时返回 IRQ_HANDLED。 } 未完待续... ....

  • 2019-01-31
  • 发表了主题帖: 芯灵思Sinlinx A33开发板 Linux中断编程 1--- 原理说明

    本节实验目标实现按键触发中断终端显示按键松开或按下 实验平台 芯灵思Sinlinx A33 开发板 step1 查看原理图,三个按键都连接到LRADC0引脚,通过判断电压大小来确定是按的哪个键。 step2 内核关于 CPU 的中断号linux 中断注册函数中的 irq 中断号并不是芯片物理上的编号,而是由芯片商在移植 Linux 系统时定在构架相 关的头文件中定义好的, 在内核源码中,名字一般是 irqs.h。       打开vim /root/work/sinlinx/a33/lichee/linux-3.4/arch/arm/mach-sunxi/include/mach/irqs.h 这里全志A33 是#include "sun8i/irqs-sun8iw5p1.h" 打开vim /root/work/sinlinx/a33/lichee/linux-3.4/arch/arm/mach-sunxi/include/mach/sun8i/irqs-sun8iw5p1.h 不知道开发板用的哪个平台,直接在.config中找 由此找到芯片在内核中的中断号 step 3  简要介绍中断驱动要用到的函数 查看 irq.h 文件  里面有关于中断的函数结构体声明 /root/work/sinlinx/a33/lichee/linux-3.4/include/linux/irq.h /** * struct irq_data - per irq and irq chip data passed down to chip functions * @irq:                interrupt number * @hwirq:              hardware interrupt number, local to the interrupt domain * @node:               node index useful for balancing * @state_use_accessors: status information for irq chip functions. *                      Use accessor functions to deal with it * @chip:               low level interrupt hardware access * @domain:             Interrupt translation domain; responsible for mapping *                      between hwirq number and linux irq number. * @handler_data:       per-IRQ data for the irq_chip methods * @chip_data:          platform-specific per-chip private data for the chip *                      methods, to allow shared chip implementations * @msi_desc:           MSI descriptor * @affinity:           IRQ affinity on SMP * * The fields here need to overlay the ones in irq_desc until we * cleaned up the direct references and switched everything over to * irq_data. */ struct irq_data {         unsigned int            irq;         unsigned long           hwirq;         unsigned int            node;         unsigned int            state_use_accessors;         struct irq_chip         *chip;         struct irq_domain       *domain;         void                    *handler_data;         void                    *chip_data;         struct msi_desc         *msi_desc; #ifdef CONFIG_SMP         cpumask_var_t           affinity; #endif }; struct irqaction 结构体在 /root/work/sinlinx/a33/lichee/linux-3.4/include/linux/interrupt.h /** * struct irqaction - per interrupt action descriptor * @handler:    interrupt handler function * @flags:      flags (see IRQF_* above) * @name:       name of the device * @dev_id:     cookie to identify the device * @percpu_dev_id:      cookie to identify the device * @next:       pointer to the next irqaction for shared interrupts * @irq:        interrupt number * @dir:        pointer to the proc/irq/NN/name entry * @thread_fn:  interrupt handler function for threaded interrupts * @thread:     thread pointer for threaded interrupts * @thread_flags:       flags related to @thread * @thread_mask:        bitmask for keeping track of @thread activity */ struct irqaction {         irq_handler_t           handler;      中断服务函数 handler         unsigned long           flags;         void                    *dev_id;         void __percpu           *percpu_dev_id;         struct irqaction        *next;         int                     irq;         irq_handler_t           thread_fn;         struct task_struct      *thread;         unsigned long           thread_flags;         unsigned long           thread_mask;         const char              *name;         struct proc_dir_entry   *dir; } ____cacheline_internodealigned_in_smp; 在interrupt.h 中有许多和中断相干的函数 exmple: request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) 功能  向内核注册一个中断服务函数,当发生中断号为 irq 的中断时候,会执行 handler 指针函数。 void free_irq(unsigned int irq, void *dev_id) 功能 从内核中断链表上删除一个中断结构 void disable_irq(unsigned int irq) 功能  关闭指定的中断,并等待中断服务函数运行结束后才会返回, 在中断函数外调用, 不能在中断服务程序中调用。 void disable_irq_nosync(unsigned int irq) 功能  关闭指定的中断,不等待中断服务函数结束,调用完这个函数立即返回。 可以中断服务函数 中调用。 void enable_irq(unsigned int irq) 功能  使能指定的中断 宏 local_save_flags(flags) 功能  禁止本 CPU 全部中断,并保存 CPU 状态信息。 宏local_irq_disable() 功能 禁止本 CPU 全部中断 Linux 内核和 GPIO 口相关的内核 API exmple: static inline int gpio_get_value(unsigned int gpio) 功能  获取指定 IO 口的电平状态 返回  IO 电平状态,非 0:表示高电平 , 0 表示低电平 static inline void gpio_set_value(unsigned int gpio, int value) 功能  设置 gpio 口的电平状态为 value 返回  IO 电平状态,非 0:表示高电平 , 0 表示低电平 static inline int gpio_to_irq(unsigned int gpio) 功能  通过 gpio 口编号获得出现这个 IO 上的外部中断编号 返回  这个 IO 上对应的外部中断编号 step 4关于 Linux 中断共享 共享中断是指多个设备共享一根中断线的情况, 在中断到来时,会遍历共享此中断的所有中断处理程序, 直 到某一个中断服务函数时返回 IRQ_HANDLED。

最近访客

< 1/1 >

统计信息

已有70人来访过

  • 芯币:344
  • 好友:3
  • 主题:89
  • 回复:50
  • 课时:--
  • 资源:--

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言