2015-04-06 12:19:55 tianyi1991 阅读数 495
  • 驱动框架入门之LED-linux驱动开发第4部分

    本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入。通过led驱动框架和gpiolib的这两个框架的详细解释,让大家学习内核中驱动框架的使用方法,这个对后续学习基于框架的复杂驱动非常有帮助。

    5851 人正在学习 去看看 朱有鹏

很久以前搞的东西,现在写一下,当保存一下。

先是驱动文件led_drv.c

#include<linux/init.h>
#include<linux/module.h>
#include<linux/io.h>
#include<linux/fs.h>
#include<asm/uaccess.h>
#include<linux/cdev.h>
#include <linux/types.h>
#include<linux/device.h>


#define GPIO5_OE 0x49056034
volatile unsigned long *gpio5_oe = NULL;
volatile unsigned long *gpio5_datain = NULL;
volatile unsigned long *gpio5_dataout = NULL;

int major;
static struct class *myclass;




static int myled_open(struct inode *inode, struct file *file)
{
    /*led配置,相应管脚输入输出*/
    *gpio5_oe &= ~( (1<<21) | (1<<22) );//配置为输出功能
    *gpio5_dataout &= ~( (1<<21) | (1<<22) );//所有输出低电平,灯灭

	return 0;
}

static ssize_t myled_read(struct file *file, char __user *buf,
			 size_t count, loff_t * ppos)

{
    printk("myled_read.....\n");
	return 0;
}

static ssize_t myled_write(struct file *file, const char __user *buf,
			  size_t count, loff_t * ppos)

{
    int val;
	copy_from_user(&val,buf,sizeof(val));
	if (1 == val)
	{
        /*开灯*/
           *gpio5_dataout |= ( (1<<22) | (1<<21) );
	}
	else
	{
      /*关灯*/
            *gpio5_dataout &= ~( (1<<22) | (1<<21) );
	}

	return 0;
	
}


static const struct file_operations myled_fops =
{
	.owner		= THIS_MODULE,
	.read		= myled_read,
	.write		= myled_write,
	//.poll		= myled_poll,
	//.ioctl		= myled_ioctl,
	.open		= myled_open,
	//.release	= myled_release,
};


static int __init myled_init(void)
{

   printk("insmod myled\n");
 
   /*内存映射,设备注册,创建类与设备*/
   gpio5_oe = (volatile unsigned long*)ioremap(GPIO5_OE,32);//32个字节
   gpio5_datain = gpio5_oe + 1;
   gpio5_dataout = gpio5_oe + 2;

   //注册设备号
   major = register_chrdev(0,"myled_drv",&myled_fops);
   myclass = class_create(THIS_MODULE,"myclass");
   device_create(myclass,NULL,MKDEV(major,0),NULL,"myled_drv");/*/sys/class/myclass/led_drv*/
   return 0;
   
}

static void __exit myled_cleanup (void)
{
/*内存解映射,设备注销,类注销*/
   iounmap(gpio5_oe);
   device_destroy(myclass,MKDEV(major,0));
   class_destroy(myclass);

   unregister_chrdev(major,"myled_drv");

   printk("rmmod myled\n");

}


module_init(myled_init);
module_exit(myled_cleanup);
MODULE_LICENSE("GPL");



























Makefile 生成模块

KERNEL_DIR = /home/tenyee/omap_prj/psp/linux-2.6.37-psp04.02.00.07.sdk
PWD := $(shell pwd)

all:
	make -C $(KERNEL_DIR) M=$(PWD) modules
obj-m := first_drv.o
clean:
	rm -rf ./*o*
执行命令:insmod first_drv.ko 

就把模块装到系统中了!

这里提一下,对于裸机单片机,也有操作IO,而且很简单,而在这里,其实也很简单。单片机与操作系统进行IO操作有什么不一样?记住下面一点:

单片机是直接操作物理地址的,而系统是操作虚拟地址的,所以,如果你想通过系统来操作一个物理地址,那就得先把物理地址映射到系统的虚拟地址中去,很简单,一个函数就好:iomap(),然后你操作返回的地址就相当于操作物理地址了,这一部分和单片机就是一样的了!!!!!

测试文件:

//#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>

/*
  usage:
  ./first_drv <on|off>
*/
int main(int argc,char *argv[])
{
   int val;
   int fd = open("/dev/myled_drv",O_RDWR);
   if (-1 == fd)
   {
      printf("can't open this file\n");
      return 0;
   }
      printf("open succeed\n");
   if (argc != 2)
   {
       printf("usage:\n%s <on|off>\n",argv[0]);
	   return 0;
   }
   
   if (0 == strcmp(argv[1],"on"))
	   val = 1;
	else 
	   val = 0;

    write(fd, &val, sizeof(val));	   
    close(fd);	 
   return 0;
}

编译:
arm-none-linux-gnueabi-gcc -static -o main main.c
 
                                                                                                                          
更多关于嵌入式的实践内容,请尽情点击:
http://blog.csdn.net/tianyi1991

2016-07-31 21:52:20 L_x_b 阅读数 869
  • 驱动框架入门之LED-linux驱动开发第4部分

    本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入。通过led驱动框架和gpiolib的这两个框架的详细解释,让大家学习内核中驱动框架的使用方法,这个对后续学习基于框架的复杂驱动非常有帮助。

    5851 人正在学习 去看看 朱有鹏

linux驱动开发LED驱动

1.1vim编写LED驱动代码

[luxibao@centos ~]$ mkdir LED

[luxibao@centos ~]$ cd LED

 

[luxibao@centos LED]$ vim s3c_led.c

/*********************************************************************************

 *      Copyright:  (C) 2016  luxibao <864809344@qq.com>

 *                  All rights reserved.

 *

 *       Filename:  s3c_led.c

 *    Description:  This file

 *

 *        Version:  1.0.0(07/31/2016~)

 *         Author:  luxibao<864809344@qq.com>

 *      ChangeLog:  1, Release initial version on "07/31/201620:32:05"

 *

 ********************************************************************************/

#include <linux/module.h>   /* Every Linux kernel module must include this head */

#include <linux/init.h>     /* Every Linux kernel module must include this head */

#include <linux/kernel.h>   /* printk() */

#include <linux/fs.h>       /* struct fops */

#include <linux/errno.h>    /* error codes */

#include <linux/cdev.h>     /* cdev_alloc()  */

#include <asm/io.h>         /* ioremap()  */

#include <linux/ioport.h>   /* request_mem_region() */

 

#include <asm/ioctl.h>      /* Linux kernel space head file for macro _IO() to generate ioctl command  */

#ifndef __KERNEL__

#include <sys/ioctl.h>     /* User space head file for macro _IO() to generate ioctl command */

#endif

//#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */

 

 

#define DRV_AUTHOR                "luxibao  <luxibao@qq.com>"

#define DRV_DESC                  "S3C24XX LED driver"

 

#define DEV_NAME                  "led"

#define LED_NUM                   4

 

/* Set the LED dev major number */

//#define LED_MAJOR                 79

#ifndef LED_MAJOR

#define LED_MAJOR                 0   //主设备号相关宏

 

#endif

 

#define DRV_MAJOR_VER             1

#define DRV_MINOR_VER             0

#define DRV_REVER_VER             0

 

#define DISABLE                   0

#define ENABLE                    1

 

#define GPIO_INPUT                0x00

#define GPIO_OUTPUT               0x01

 

 

#define PLATDRV_MAGIC             0x60  //魔术字

#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)

#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)

 

#define S3C_GPB_BASE              0x56000010  //GPB基地址及偏移地址

#define GPBCON_OFFSET             0

#define GPBDAT_OFFSET             4

#define GPBUP_OFFSET              8

#define S3C_GPB_LEN               0x10        /* 0x56000010~0x56000020  */

 

int led[LED_NUM] = {5,6,8,10};  /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */

 

static void __iomem *s3c_gpb_membase;  //定义iomem*类型变量

 

 

#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)//虚拟地址读取到物理地址

#define s3c_gpio_read(reg)       __raw_readl((reg)+s3c_gpb_membase)

 

 

int dev_count = ARRAY_SIZE(led);  //设备总数

int dev_major = LED_MAJOR; //主设备

int dev_minor = 0;  //次设备

int debug = DISABLE;

 

static struct cdev      *led_cdev;  //定义结构体指针

static int s3c_hw_init(void)   //初始化函数

{

    int          i;

    volatile unsigned long  gpb_con, gpb_dat, gpb_up;

 

    if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))//判断是否成功申请一段内存空间

    {

        return -EBUSY;

    }

 

    if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )//判断虚拟地址到物理地址的映射

    {

        release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);

        return -ENOMEM;

    }

 

    for(i=0; i<dev_count; i++)

    {

        /* Set GPBCON register, set correspond GPIO port as input or output mode  */

        gpb_con = s3c_gpio_read(GPBCON_OFFSET);

        gpb_con &= ~(0x3<<(2*led[i]));   /* Clear the currespond LED GPIO configure register */

        gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */

        s3c_gpio_write(gpb_con, GPBCON_OFFSET);

 

        /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable  */

        gpb_up = s3c_gpio_read(GPBUP_OFFSET);

        //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */

        gpb_up |= (0x1<<led[i]);  /* Disable pull up resister */

        s3c_gpio_write(gpb_up, GPBUP_OFFSET);

 

        /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */

        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);

        //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */

        gpb_dat |= (0x1<<led[i]);  /* This port set to high level, then turn LED off */

        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);

    }

 

    return 0;

}

 

static void turn_led(int which, unsigned int cmd) //led函数的操作

{

    volatile unsigned long  gpb_dat;

 

    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);

 

    if(LED_ON == cmd)

    {

        gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */

    }

    else if(LED_OFF == cmd)

    {

        gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */

    }

 

    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);

}

 

static void s3c_hw_term(void) //清除led操作函数

{

    int                     i;

    volatile unsigned long  gpb_dat;

 

    for(i=0; i<dev_count; i++)

    {

        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);

        gpb_dat |= (0x1<<led[i]);  /* Turn LED off */

        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);

    }

 

    release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //释放I/O内存

    iounmap(s3c_gpb_membase);   //iounmap函数取消iounmap()做的映射

}

 

static int led_open(struct inode *inode, struct file *file)//led开启函数

{

    int minor = iminor(inode);   //获取次设备号

 

    file->private_data = (void *)minor;  //保存到一个全局变量

 

    printk(KERN_DEBUG "/dev/led%d opened.\n", minor);

    return 0;

}

 

static int led_release(struct inode *inode, struct file *file)  //led关闭函数

{

    printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));

 

    return 0;

}

 

static void print_help(void)      //帮助信息

{

    printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);

    //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);

    printk("Turn LED on command  : %u\n", LED_ON);

    printk("Turn LED off command : %u\n", LED_OFF);

 

    return;

}

 

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  //ioctl函数

{

    int which = (int)file->private_data; //接收次设备号

 

    switch (cmd)   //判断led开关

    {

        case LED_ON:

 

            turn_led(which, LED_ON);

            break;

 

        case LED_OFF:

            turn_led(which, LED_OFF);

            break;

 

        default:

            printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);

            print_help();

            break;

    }

 

    return 0;

}

 

 

static struct file_operations led_fops =            //fops函数

{

    .owner = THIS_MODULE,

    .open = led_open,

    .release = led_release,

    .unlocked_ioctl = led_ioctl,

};

 

static int __init s3c_led_init(void)         //init函数

{

    int                    result;

    dev_t                  devno;

 

    if( 0 != s3c_hw_init() )     //做初始化的判断

    {

        printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");

        return -ENODEV;

    }

 

    /*  Alloc the device for driver */

    if (0 != dev_major) /*  Static */     //静态分配主,次设备号转化为dev_t设备编号

    {

        devno = MKDEV(dev_major, 0);

        result = register_chrdev_region (devno, dev_count, DEV_NAME);

    }

    else

    {

        result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);//动态分配主,次设备号

        dev_major = MAJOR(devno);

    }

 

    /*  Alloc for device major failure */

    if (result < 0)     //分配失败则打印

    {

        printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);

        return -ENODEV;

}

printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);

 

    if(NULL == (led_cdev=cdev_alloc()) ) //led_cddv分配失败打印

    {

        printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);

        unregister_chrdev_region(devno, dev_count);

        return -ENOMEM;

    }

 

    led_cdev->owner = THIS_MODULE;  //分配成功,进行参数定义

    cdev_init(led_cdev, &led_fops);  //链接led_cdev和led_fops

 

    result = cdev_add(led_cdev, devno, dev_count);//添加内存

    if (0 != result)      //出错打印

    {

        printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);

        goto ERROR;

    }

 

 

    printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n",

            DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);  //打印成功

    return 0;

 

 

ERROR:                 //内存出错执行

    printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);

    cdev_del(led_cdev);

    unregister_chrdev_region(devno, dev_count);

    return result;

}

static void __exit s3c_led_exit(void)     //led退出函数

{

    dev_t devno = MKDEV(dev_major, dev_minor);

 

    s3c_hw_term();      //调用清除led操作函数

 

    cdev_del(led_cdev);   //撤销

    unregister_chrdev_region(devno, dev_count);

 

    printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n",

            DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);

 

    return ;

}

 

 

 

/* These two functions defined in <linux/init.h> */

module_init(s3c_led_init);

module_exit(s3c_led_exit);

 

module_param(debug, int, S_IRUGO);

module_param(dev_major, int, S_IRUGO);

 

MODULE_AUTHOR(DRV_AUTHOR);

MODULE_DESCRIPTION(DRV_DESC);

MODULE_LICENSE("GPL");

以上就是我们的LED相关驱动模块函数。

先是调用module_init函数,在调用s3c_led_init函数,这个函数会调用 s3c_hw_init进行初始化;接着s3c_led_init函数会进行分配主次设备号,按照传入的参数判断是动态分配或者是静态分配;再者是分配结构体,如果链接owner成功就可接着连接fops;然后是ON 和OFF的实现功能,使用ioctl与装入卸载次设备号驱动以及模块信息和fops中与主次设备号结构体链接,ioctl通过传参来实现相应的功能;最后进行撤销。

 

1.2编写Makefile

[luxibao@centos LED]$ vim Makefile

C=/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc

KDIR?=/home/luxibao/fl2440/kernel/linux-3.0

obj-m:=s3c_led.o

 

default:

        $(MAKE) -C $(KDIR) M=`pwd` modules

        make clean

 

clean:

        rm -f *.o *mod.c *.order *.symvers

 

[luxibao@centos LED]$ make

make -C /home/luxibao/fl2440/kernel/linux-3.0 M=`pwd` modules

make[1]: Entering directory `/home/luxibao/fl2440/kernel/linux-3.0'

  CC [M]  /home/luxibao/LED/s3c_led.o

  Building modules, stage 2.

  MODPOST 1 modules

  CC      /home/luxibao/LED/s3c_led.mod.o

  LD [M]  /home/luxibao/LED/s3c_led.ko

make[1]: Leaving directory `/home/luxibao/fl2440/kernel/linux-3.0'

make clean

make[1]: Entering directory `/home/luxibao/LED'

rm -f *.o *mod.c *.order *.symvers

make[1]: Leaving directory `/home/luxibao/LED'

[luxibao@centos LED]$ ls

Makefile  s3c_led.c  s3c_led.ko

 

2.1编写LED灯程序,点亮LED

[luxibao@centos LED]$ mkdir LED_test

[luxibao@centos LED]$ ls

LED_test  Makefile  s3c_led.c  s3c_led.ko

[luxibao@centos LED]$ cd LED_test

[luxibao@centos LED_test]$ vim test_s3c_led.c

 

/*********************************************************************************

 *      Copyright:  (C) 2016 luxibao<864809344@qq.com>

 *                  All rights reserved.

 *

 *       Filename:  test_s3c_led.c

 *    Description:  This file

 *

 *        Version:  1.0.0(07/31/2016)

 *         Author:  luxibao <864809344@qq.com>

 *      ChangeLog:  1, Release initial version on "07/31/2016 20:32:22"

 *

 ********************************************************************************/

#include <stdio.h>

#include <stdarg.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <unistd.h>

 

 

#define LED_CNT        4

#define DEVNAME_LEN    10

 

#define PLATDRV_MAGIC             0x60

#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)

#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)

 

/********************************************************************************

 *  Description:

 *   Input Args:

 *  Output Args:

 * Return Value:

 ********************************************************************************/

int main (int argc, char **argv)

{

    int           i;

    int           fd[LED_CNT];

    char          dev_name[DEVNAME_LEN]={0,0,0,0};

 

    for(i=0; i<LED_CNT; i++)

    {

        snprintf(dev_name, sizeof(dev_name), "/dev/led%d", i);

        fd[i] = open(dev_name, O_RDWR, 0755);

        if(fd[i] < 0)

            goto err;

    }

 

    while(1)

    {

        for(i=0; i<LED_CNT; i++)

        {

            ioctl(fd[i], LED_ON);

        }

        sleep(1);

 

        for(i=0; i<LED_CNT; i++)

        {

            ioctl(fd[i], LED_OFF);

        }

        sleep(1);

    }

 

    for(i=0; i<LED_CNT; i++)

    {

        close(fd[i]);

    }

 

    return 0;

 

err:

    for(i=0; i<LED_CNT; i++)

    {

        if(fd[i] >= 0)

        {

            close(fd[i]);

        }

    }

    return -1;

} /* ----- End of main() ----- */

 

"test_s3c_led.c" 81L, 1952C 已写入                             

[luxibao@centos LED_test]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc test_s3c_led.c

[luxibao@centos LED_test]$ ls

a.out  test_s3c_led.c

 

应用程序使用交叉编译器编译得到a.out。

我们的开发板上将  .ko文件和 a.out 文件 tftp 过去。这板子上会有我们这两个文件,注意这两个文件的权限需要改成可执行。

insmod 文件名.ko  就可以看到我们的主设备号。  如major=251  

 

在开发板上执行的操作:

>: tftp -gr s3c_led.ko 192.168.1.2

s3c_led.ko           100% |*******************************|  6214   0:00:00 ETA

>: tftp -gr a.out 192.168.1.2

a.out                100% |*******************************|  5742   0:00:00 ETA

>: chmod 777 a.out

>: chmod 777 s3c_led.ko

>: insmod s3c_led.ko

S3C led driver[major=251] version 1.0.0 installed successfully!

>: mknod /dev/led0 c 251 0   //手动创建结点

>: mknod /dev/led1 c 251 1

>: mknod /dev/led2 c 251 2

>: mknod /dev/led3 c 251 3

>: ls -l dev/led* //查看我们的设备

crw-r--r--    1 root     root      251,   0 Dec 31 17:05 dev/led0

crw-r--r--    1 root     root      251,   1 Dec 31 17:05 dev/led1

crw-r--r--    1 root     root      251,   2 Dec 31 17:05 dev/led2

crw-r--r--    1 root     root      251,   3 Dec 31 17:05 dev/led3

>: ll a.out

-rwxrwxrwx    1 root     root          5742 Dec 31 17:01 a.out

>: chmod 777 a.out     //权限需要改成可执行

>: ./a.out

最后 ./a.out   LED灯就能在开发板上跑起来,这就可以称为跑马灯

2016-07-26 11:42:11 LYX_WIN 阅读数 805
  • 驱动框架入门之LED-linux驱动开发第4部分

    本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入。通过led驱动框架和gpiolib的这两个框架的详细解释,让大家学习内核中驱动框架的使用方法,这个对后续学习基于框架的复杂驱动非常有帮助。

    5851 人正在学习 去看看 朱有鹏

———————————————————————————————————————

主机操作系统:Centos 6.7
交叉编译器环境:arm-linux-gcc-4.5.4 
开发板平台: FL2440 
Linux内核版本: linux-3.0 
开发模块: LED
邮箱:leiyuxing205@gmail.com

———————————————————————————————————————

1.1vim编写LED驱动代码

 

[leiyuxing@centos6 ~]$ mkdir LED

[leiyuxing@centos6 LED]$ vim s3c_led

/*********************************************************************************
 *      Copyright:  (C) 2016  leiyuxing <leiyuxing@gmail.com>
 *                  All rights reserved.
 *
 *       Filename:  s3c_led.c
 *    Description:  This file
 *
 *        Version:  1.0.0(07/25/2016~)
 *         Author:  leiyuxing <leiyuxing@gmail.com>
 *      ChangeLog:  1, Release initial version on "07/25/2016 16:46:40 PM"
 *
 ********************************************************************************/
#include <linux/module.h>   /* Every Linux kernel module must include this head */
#include <linux/init.h>     /* Every Linux kernel module must include this head */
#include <linux/kernel.h>   /* printk() */
#include <linux/fs.h>       /* struct fops */
#include <linux/errno.h>    /* error codes */
#include <linux/cdev.h>     /* cdev_alloc()  */
#include <asm/io.h>         /* ioremap()  */
#include <linux/ioport.h>   /* request_mem_region() */
 
#include <asm/ioctl.h>      /* Linux kernel space head file for macro _IO() to generate ioctl command  */
#ifndef __KERNEL__
#include <sys/ioctl.h>     /* User space head file for macro _IO() to generate ioctl command */
#endif
//#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */
 
 
#define DRV_AUTHOR                "leiyuxing  <leiyuxing@gmail.com>"
#define DRV_DESC                  "S3C24XX LED driver"
 
#define DEV_NAME                  "led" //定义设备名称
#define LED_NUM                   4  //定义设备数量
 
/* Set the LED dev major number */
//#define LED_MAJOR                 79
#ifndef LED_MAJOR
#define LED_MAJOR                 0   //定义主设备号为0,一般这个定义设备号是固定不可用的,但是 这为自动分配主设备号。
 
#endif
 
#define DRV_MAJOR_VER             1
#define DRV_MINOR_VER             0
#define DRV_REVER_VER             0
 
#define DISABLE                   0//出现错误定义为0
#define ENABLE                    1//出现错误定义为1(两个错误这样定义应该是不同函数错误返回值不同)
 
#define GPIO_INPUT                0x00//宏定义 GPIO输入模式用0代替
#define GPIO_OUTPUT               0x01//宏定义 GPIO输出模式用1代替
 
 
#define PLATDRV_MAGIC             0x60  //魔术字
#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)//_IO是Core_cm3.h中被重定义:#define     _IO     volatile的作用就是指示因优化而省略此指令
#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)
 
#define S3C_GPB_BASE              0x56000010  //GPB基地址及偏移地址
#define GPBCON_OFFSET             0
#define GPBDAT_OFFSET             4
#define GPBUP_OFFSET              8
#define S3C_GPB_LEN               0x10        /* 0x56000010~0x56000020  */
 
int led[LED_NUM] = {5,6,8,10};  /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
 
static void __iomem *s3c_gpb_membase;  //定义iomem*类型变量
 
 
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)//虚拟地址读取到物理地址
#define s3c_gpio_read(reg)       __raw_readl((reg)+s3c_gpb_membase)
 
 
int dev_count = ARRAY_SIZE(led);  //设备总数
int dev_major = LED_MAJOR; //主设备
int dev_minor = 0;  //次设备
int debug = DISABLE;
 
static struct cdev      *led_cdev;  //定义结构体指针
static int s3c_hw_init(void)   //初始化函数
{
    int          i;
    volatile unsigned long  gpb_con, gpb_dat, gpb_up;
 
    if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))//判断是否成功申请一段内存空间
    {
        return -EBUSY;
    }
 
    if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )//判断虚拟地址到物理地址的映射
    {
        release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);
        return -ENOMEM;
    }
 
    for(i=0; i<dev_count; i++)
    {
        /* Set GPBCON register, set correspond GPIO port as input or output mode  */
        gpb_con = s3c_gpio_read(GPBCON_OFFSET);
        gpb_con &= ~(0x3<<(2*led[i]));   /* Clear the currespond LED GPIO configure register */
        gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */
        s3c_gpio_write(gpb_con, GPBCON_OFFSET);
 
        /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable  */
        gpb_up = s3c_gpio_read(GPBUP_OFFSET);
        //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */
        gpb_up |= (0x1<<led[i]);  /* Disable pull up resister */
        s3c_gpio_write(gpb_up, GPBUP_OFFSET);
 
        /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */
        gpb_dat |= (0x1<<led[i]);  /* This port set to high level, then turn LED off */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }
 
    return 0;
}
 
static void turn_led(int which, unsigned int cmd) //led函数的操作
{
    volatile unsigned long  gpb_dat;
 
    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
 
    if(LED_ON == cmd)
    {
        gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */
    }
    else if(LED_OFF == cmd)
    {
        gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */
    }
 
    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
 
static void s3c_hw_term(void) //清除led操作函数
{
    int                     i;
    volatile unsigned long  gpb_dat;
 
    for(i=0; i<dev_count; i++)
    {
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        gpb_dat |= (0x1<<led[i]);  /* Turn LED off */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }
 
    release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //释放I/O内存
    iounmap(s3c_gpb_membase);   //iounmap函数取消iounmap()做的映射
}
 
static int led_open(struct inode *inode, struct file *file)//led开启函数
{
    int minor = iminor(inode);   //获取次设备号
 
    file->private_data = (void *)minor;  //保存到一个全局变量
 
    printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
    return 0;
}
 
static int led_release(struct inode *inode, struct file *file)  //led关闭函数
{
    printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
 
    return 0;
}
 
static void print_help(void)      //帮助信息
{
    printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
    //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
    printk("Turn LED on command  : %u\n", LED_ON);
    printk("Turn LED off command : %u\n", LED_OFF);
 
    return;
}
 
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  //ioctl函数
{
    int which = (int)file->private_data; //接收次设备号
 
    switch (cmd)   //判断led开关
    {
        case LED_ON:
 
            turn_led(which, LED_ON);
            break;
 
        case LED_OFF:
            turn_led(which, LED_OFF);
            break;
 
        default:
            printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
            print_help();
            break;
    }
 
    return 0;
}
 
 
static struct file_operations led_fops =            //fops函数
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
};
 
static int __init s3c_led_init(void)         //init函数
{
    int                    result;
    dev_t                  devno;
 
    if( 0 != s3c_hw_init() )     //做初始化的判断
    {
        printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
        return -ENODEV;
    }
 
    /*  Alloc the device for driver */
    if (0 != dev_major) /*  Static */     //静态分配主,次设备号转化为dev_t设备编号
    {
        devno = MKDEV(dev_major, 0);
        result = register_chrdev_region (devno, dev_count, DEV_NAME);
    }
    else
    {
        result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);//动态分配主,次设备号
        dev_major = MAJOR(devno);
    }
 
    /*  Alloc for device major failure */
    if (result < 0)     //分配失败则打印
    {
        printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
        return -ENODEV;
}
printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);
 
    if(NULL == (led_cdev=cdev_alloc()) ) //led_cddv分配失败打印
    {
        printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
        unregister_chrdev_region(devno, dev_count);
        return -ENOMEM;
    }
 
    led_cdev->owner = THIS_MODULE;  //分配成功,进行参数定义
    cdev_init(led_cdev, &led_fops);  //链接led_cdev和led_fops
 
    result = cdev_add(led_cdev, devno, dev_count);//添加内存
    if (0 != result)      //出错打印
    {
        printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);
        goto ERROR;
    }
 
 
    printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n",
            DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);  //打印成功
    return 0;
 
 
ERROR:                 //内存出错执行
    printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
    cdev_del(led_cdev);
    unregister_chrdev_region(devno, dev_count);
    return result;
}
static void __exit s3c_led_exit(void)     //led退出函数
{
    dev_t devno = MKDEV(dev_major, dev_minor);
 
    s3c_hw_term();      //调用清除led操作函数
 
    cdev_del(led_cdev);   //撤销
    unregister_chrdev_region(devno, dev_count);
 
    printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n",
            DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);
 
    return ;
}
 
 
 
/* These two functions defined in <linux/init.h> */
module_init(s3c_led_init);
module_exit(s3c_led_exit);
 
module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO);
 
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
以上就是我们的LED相关驱动模块函数。

先是调用module_init函数,在调用s3c_led_init函数,这个函数会调用 s3c_hw_init进行初始化;接着s3c_led_init函数会进行分配主次设备号,按照传入的参数判断是动态分配或者是静态分配;再者是分配结构体,如果链接owner成功就可接着连接fops;然后是ON 和OFF的实现功能,使用ioctl与装入卸载次设备号驱动以及模块信息和fops中与主次设备号结构体链接,ioctl通过传参来实现相应的功能;最后进行撤销。

 

 1.2编写Makefile

[leiyuxing@centos6 LED]$ vim Makefile

C=/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc
KDIR?=/home/leiyuxing/fl2440/kernel/linux-3.0
obj-m:=s3c_led.o
 
default:
        $(MAKE) -C $(KDIR) M=`pwd` modules
        make clean
 
clean:
        rm -f *.o *mod.c *.order *.symvers
make会生   .ko  文件 这就是我们所需要加载的驱动模块。

[leiyuxing@centos6 LED]$ ls

Makefile  s3c_led.c  s3c_led.ko

2.1编写LED灯程序,点亮LED

[leiyuxing@centos6 LED_test]$ vim led_test.c

/*********************************************************************************
 *      Copyright:  (C) 2016 leiyuxing <leiyuxing@gmail.com>
 *                  All rights reserved.
 *
 *       Filename:  led_test.c
 *    Description:  This file
 *                 
 *        Version:  1.0.0(07/25/2016)
 *         Author:  leiyuxing  <leiyuxing@gmail.com>
 *      ChangeLog:  1, Release initial version on "07/25/2016 19:36:22 PM"
 *                 
 ********************************************************************************/
 
 
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
 
#define LED_NUM         4
#define DEVNAME_LEN     10
 
 
 
#define PLATDRV_MAGIC             0x60
#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)
 
int main(int argc, char **argv)
{
        int fd[LED_NUM];
        int     i;
        int     j;
        char devname[DEVNAME_LEN] = {0};
 
        for(i = 0; i < LED_NUM; i++)
        {
                snprintf(devname, sizeof(devname), "/dev/led%i",i);
                fd[i] = open(devname, O_RDWR,755);
                if(fd[i] < 0)
                {
                        printf("Can not open %s: %s", devname, strerror(errno));
                        for(j = 0; j < i; j++)
                        {
                                if(fd[j] > 0)
                                        close(fd[j]);
                        }
                        return -1;
                }
        }
 
        while(1)
        {
                for(i = 0; i < LED_NUM; i++)
                {
                        ioctl(fd[i], LED_ON);
                        sleep(1);
                        ioctl(fd[i], LED_OFF);
                }
        }
 
        for(i = 0; i < LED_NUM; i++)
        {
                close(fd[i]);
        }
        return 0;
}

[leiyuxing@centos6 LED_test]$/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc led_test.c

[leiyuxing@centos6 LED_test]$ ls

a.out  led_test.c

 

应用程序使用交叉编译器编译得到a.out。

在我们的开发板上将  .ko文件和 a.out 文件 tftp 过去。这是板子上会有我们这两个文件,注意这两个文件的权限需要改成可执行。

insmod 文件名.ko  就可以看到我们的主设备号。  如major=251  

 

 

在开发板上执行的操作:

 

>: tftp -gr s3c_led.ko 192.168.1.2

s3c_led.ko           100% |*******************************|  6210   0:00:00 ETA

>: tftp -gr a.out 192.168.1.2

a.out                100% |*******************************|  6007   0:00:00 ETA

>: insmod s3c_led.ko

S3C led driver[major=251] version 1.0.0 installed successfully!

>: mknod /dev/led0 c 251 0   //手动创建结点

>: mknod /dev/led1 c 251 1

>: mknod /dev/led2 c 251 2

>: mknod /dev/led3 c 251 3

>: ls -l dev/led* //查看我们的设备

crw-r--r--    1 root     root      251,   0 Dec 31 17:20 dev/led0

crw-r--r--    1 root     root      251,   1 Dec 31 17:20 dev/led1

crw-r--r--    1 root     root      251,   2 Dec 31 17:21 dev/led2

crw-r--r--    1 root     root      251,   3 Dec 31 17:21 dev/led3

>: ll a.out

-rw-r--r--    1 root     root          6007 Dec 31 17:17 a.out

>: chmod 777 a.out

>: ./a.out

最后 ./a.out   LED灯就能在开发板上跑起来,这就可以称为跑马灯

出现问题:

问题一:


 

没有权限

解决方法:

>: ll a.out

-rw-r--r--    1 root     root          6007 Dec 31 17:17 a.out

>: chmod 777 a.out

 

 

2019-07-09 01:53:43 weixin_30919919 阅读数 540
  • 驱动框架入门之LED-linux驱动开发第4部分

    本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入。通过led驱动框架和gpiolib的这两个框架的详细解释,让大家学习内核中驱动框架的使用方法,这个对后续学习基于框架的复杂驱动非常有帮助。

    5851 人正在学习 去看看 朱有鹏

这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的。个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门。

不能再扯了,涉及到linux的驱动开发知识面灰常广,再扯文章就会变得灰常长。首先还是回到led驱动的本身上,自从linux被移植到arm上后,做驱动开发的硬件知识要求有所降低,很多都回归到了软件上,这是系统编程的一大特点,当然 ,也不排除有很多设备的驱动需要我们从头做起。

led的驱动虽然看似很简单,但是要描述清楚估计上万字都不一定够用,本篇文章从初学者的角度出发,重点关注在整个软件的开发的流程和思想,而不过多局限与细节的分析,初学者应该首先把握某一类编程的流程和思想,这样才能入门快,进步迅速。作为一个初学者我一直觉得这样入门效率最高。

下面我们进入主题led驱动的书写:

既然是在linux系统下设备驱动开发,就不同于以往我们单片机下设置一个高电平而了事,在linux系统下开发的驱动程序要想在linux正常工作,一定要符合linux系统的规范,linux下设备被分为三个类型字符设备/块设备/网络设备。上述的三种设备不可能面面俱到,因此还提出了一个杂设备作为补充,看了下网上大部分人都把led的驱动设备归到了杂设备,这是为什么呢?原来是友善之臂的手册上把它归为了杂设备,哈哈,所以杂设备这种版本比较流行,木有追求的人们啊! 那我们把它归为那种设备呢?当然是杂设备了,嘿嘿。。。。。。

既然对led我们准备把它作为一个杂设备加入系统,是不是应该有一个名字吧,还应该有个操作符号吧。。。。。。

stop,停止你的YY,关于这个设备的标准形式大神已经帮你定义好了,具体它存在于系统的include下,里面有一个miscdivice.h。

 

 1 struct miscdevice  {
 2         int minor;
 3         const char *name;
 4         const struct file_operations *fops;
 5         struct list_head list;
 6         struct device *parent;
 7         struct device *this_device;
 8         const char *nodename;
 9         mode_t mode;
10 };

 

好吧,原来为了统一规范,我们只需按照标准来填充内容就可以啦!要想正确的使用这个描述设备的结构体,必须清楚的了解到其中的每个成员。天空飘来四个字 f u c k

好,让我们平复一下心情,继续了解它。查了下minor这个单词是次要的,在这里是次设备号的意思。这是因为杂设备为了节约主设备号,采用共用主设备号的方式,次设备号加以区分的方式来描述设备,因此来看这个minor是要必须填写啦!不愧是过了四级的人,第二个直接看懂啦!欧耶~

之前做过一个了解,这第三个在linux驱动中非常的重要,可以称之为核心,我们很大的工作 都要围绕这个file_operations来操作,因此必须要隆重的研究下这个file_operatios这个结构体。

file_operations这个结构体的存在是linux中将所有设备看做文件的基础,这是为什么呢?因为通俗的说就是这个结构体是文件操作和驱动操作的一个关系映射,对于系统的操作函数(诸如read/write)在这个结构体里都有与之对应的对硬件进行操作的函数。wow这个函数居然如此之酷!这样以来,我们还弄清楚了另外一个问题,就是为什么我们不能直接越过操作系统来操作硬件,都是因为有它啊!可见这个结构体在内核中的地位,以及在linux操作系统中的地位。哈哈下面的几个成员,就先不分析啦!我们这次也用不上,感觉在linux下开发驱动真是个力气活啊!

见过file_operations的厉害之后,我们自然知道 现在只要把这个结构体弄清楚就可以敲代码写驱动啦!so,let‘s go!

首先在系统目录include/linux/fs.h中找到这个牛逼的结构体:

 

 1 struct file_operations {
 2         struct module *owner;
 3         loff_t (*llseek) (struct file *, loff_t, int);
 4         ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
 5         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
 6         ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
 7         ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
 8         int (*readdir) (struct file *, void *, filldir_t);
 9         unsigned int (*poll) (struct file *, struct poll_table_struct *);
10         int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
11         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
12         long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
13         int (*mmap) (struct file *, struct vm_area_struct *);
14         int (*open) (struct inode *, struct file *);
15         int (*flush) (struct file *, fl_owner_t id);
16         int (*release) (struct inode *, struct file *);
17         int (*fsync) (struct file *, struct dentry *, int datasync);
18         int (*aio_fsync) (struct kiocb *, int datasync);
19         int (*fasync) (int, struct file *, int);
20         int (*lock) (struct file *, int, struct file_lock *);
21         ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
22         unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
23          int (*check_flags)(int);
24         int (*flock) (struct file *, int, struct file_lock *);
25         ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
26         ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
27         int (*setlease)(struct file *, long, struct file_lock **);
28 };
View Code

 

 

当我知道有这么多成员的时候,当时我就尿了,不过,还好我们只需要实现本次驱动需要的东东!查看了一下手册上的驱动,欧耶~这次我们只需要研究两个成员ower和ioctl。上面可以看出ower是一个结构体成员就是是那个模块,而ioctl是个函数。

那为什么是ioctl函数呢?这个问题弄清楚灰常重要!因为我们前边说过file_operations内的函数是对文件操作的的映射,我们要控制led,实质是控制IO口的电平,这点不管它操作系统的驱动还是单片机的驱动实质都是一样的啦!此时,我瞬间明白鸟,icctl不就是IO Control的缩写吗?不就是文件的控制IO吗?因为我们要控制led,所以需要控制IO口,要控制IO对应的系统调用函数不就是ioctl吗?我们在驱动中需要做的就是给这个ioctl函数进行编写,然后系统就可以通过调用ioctl这个系统函数来通过file_operactions来关联到真正的驱动函数。欧耶 ~      终于都清楚啦!

现在,软件方面的核心都打通啦!既然是驱动,当然少不了硬件啦!

这四个led与固定的四个IO口连接,像这样的东西开发板原理图上是交代的很清楚的,我们必须按照开发板上的关系来进行。这四个led占用的IO寄存器分别为GPB5/GPB6/GPB7/GPB8;这就 好比当年我们单片机的P0/P1一样一样的,P0/P1都被定义在了reg51.h里面,而这里的IO口同样也被定义到了<arch/regs-gpio.h>里,这里我们声明这四个IO口寄存器,这样我们操作IO口就可以控制led了。

 

1 static unsigned long led_table []= {
2         S3C2410_GPB(5),
3         S3C2410_GPB(6),
4         S3C2410_GPB(7),
5         S3C2410_GPB(8),
6 };

 

 

这里需要注意的是与led连接的GPIO口可以用于输入/输出或者其他功能,我们的开发板上led接的是共阳极的,所以我们需要这些GPIO口作为输出口,只要我们输出低电平就可以让led亮了。既然这个IO口有多种功能,那一定有相关的配置寄存器 。所以我们需要将每个led对应的寄存器定义为输出。

 

1 static unsigned int led_cfg_table  []={
2         S3C2410_GPIO_OUTPUT,
3         S3C2410_GPIO_OUTPUT,
4         S3C2410_GPIO_OUTPUT,
5         S3C2410_GPIO_OUTPUT,
6 };

 

做好了前面的所有工作,现在就是驱动的核心了。如何控制 ?这的确是一个question?这时忽然想起了,前面分析的file_operations,这个玩意不就是系统和驱动的纽带吗?控制led系统需要ioctl函数,所以在flie_operations中我们也需要一个和驱动直接联系的ioctl函数,那么我们命名这个函数为heatnan_leds_ioctl;函数的原型前面file_operaction 中已经给出了,有种直接领表填单的赶脚!而且下面的开发思路也都清晰起来,那就是需要什么样的功能直接参考file_operactions结构体的参数模型就是了。

针对于led的驱动,要实现应用程序控制led的亮灭——>需要系统调用ioctl函数——>要使系统的ioctl函数能够控制硬件——>需要在file_operations中建立一个真正控制led驱动的函数——>新建控制led的函数(这里命名为heatnan_leds_ioctl).

首先我们建立连接关系:

1 static struct file_operations dev_fops={
2         .owner = THIS_MODULE,
3         .ioctl= heatnan_leds_ioctl,
4 };

 

建立这个核心纽带后,就要书写heatnan_leds_ioctl这个函数啦!

要书写这个函数必须要对这个函数的两种形式有所了解,即需要对ioctl函数做功课,ioctl在系统函数中有三个参数,第三个参数可选,第一个参数代表操作设备的号,第二个参数代表操作命令,第三个可选参数可以以不同数据类型作为参数传递也不是必须的。

与之在file_operactions中对应的ioctl则多了一个参数,它的前两个参数对应系统函数的第一个参数,控制命令则进行原封不动的接收。

 1 static int heatnan_leds_ioctl(
 2         struct inode *inode,
 3         struct file *file,
 4         unsigned int cmd,
 5         unsigned long arg)
 6 {
 7        if(arg<0||arg>3)
 8        {
 9            return -EINVAL;
10        }
11        switch(cmd)
12        {
13             case 0:
14             case 1:
15                 {
16                       s3c2410_gpio_setpin(led_table[arg],!cmd);
17                       return 0;
18                 }
19             default:
20                       return -EINVAL;
21 
22        }
23  }
View Code

 

当你翻开代码一看居然有一些诸如s3c2410_gpio_setpin的函数的时候,你一定心中一声感叹!艹,这个函数那来的?

为什么会有这些函数呢?原因是linux平台对arm体系是有支持的,比如这些基本的函数,当我们开发程序的时候就可以用,有人觉得为什么要用它的自己写不是更cool吗?个人觉得从学习角度来说未尝不可,但从开发觉得还是高效最重要,为什么在应用程序里C++比C更流行,正是因为C++的效率更高,有更多库的支持,因此我觉得不管是我们软件应用开发也好或者硬件的应用开发也好,开发的难度一定是越来越小,开发效率是越来越高!所以,平台给我们提供的函数能用则用!

这个函数一旦建立,我们就可以通过通过操作系统的ioctl函数来间接操作heatnan_leds.ioctl,从而控制led。

下面就是一些程式化的东西了,模块的初始化,模块的退出以及设备的注册等一些列比较俗的问题了。

×××××××××××××××××××累的赶脚,活剥不起来la,linux驱动开发感觉累累的!@

下面贴出整个led驱动代码,主要是仿照数据手册写的,这次主要是入门的学习以及对驱动开发的理解。明天就要进入脱离数据手册的驱动编写辣!好赤鸡的感觉!

 1 #include<linux/kernel.h>
 2 #include<linux/module.h>
 3 #include<linux/miscdevice.h>
 4 #include<linux/gpio.h>
 5 #include<linux/fs.h>
 6 #include<linux/init.h>
 7 #include<mach/regs-gpio.h>
 8 #include<mach/hardware.h>
 9 
10 #define DEVICE_NAME "heat_leds"
11 
12 
13 static unsigned long led_table []= {
14         S3C2410_GPB(5),
15         S3C2410_GPB(6),
16         S3C2410_GPB(7),
17         S3C2410_GPB(8),
18 };
19 static unsigned int led_cfg_table  []={
20         S3C2410_GPIO_OUTPUT,
21         S3C2410_GPIO_OUTPUT,
22         S3C2410_GPIO_OUTPUT,
23         S3C2410_GPIO_OUTPUT,
24 };
25 static int heatnan_leds_ioctl(
26         struct inode *inode,
27         struct file *file,
28         unsigned int cmd,
29         unsigned long arg)
30 {
31        if(arg<0||arg>3)
32        {
33            return -EINVAL;
34        }
35        switch(cmd)
36        {
37             case 0:
38             case 1:
39                 {
40                       s3c2410_gpio_setpin(led_table[arg],!cmd);
41                       return 0;
42                 }
43             default:
44                       return -EINVAL;
45 
46        }
47 }
48 static struct file_operations dev_fops={
49         .owner = THIS_MODULE,
50         .ioctl= heatnan_leds_ioctl,
51 };
52 static struct miscdevice misc={
53         .minor=MISC_DYNAMIC_MINOR,
54         .name=DEVICE_NAME,
55         .fops=&dev_fops,
56 };
57 static int __init led_init(void)
58 {
59         int ret;
60         int i;
61         for(i=0;i<4;i++)
62         {
63                 s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
64                 s3c2410_gpio_cfgpin(led_table[i],0);
65         }
66         ret=misc_register(&misc);
67         printk(DEVICE_NAME"\tinitialized\n");
68         return ret;
69 }
70 static void __exit led_exit(void)
71 {
72         misc_deregister(&misc);
73 }
74 module_init(led_init);
75 module_exit(led_exit);
76 MODULE_LICENSE("GPL");
View Code

终于tmd写完了这个驱动函数,可如果事情到这里结束那也是相当完美啊!按照昨天的方法顺利把驱动模块化编译成功弄到了开发板上,

结果

。。。。。。。。

结果,烧上驱动测试程序后灯真的没亮,我用lsmod和dev下豆发现了模块正在运行,灯就是不亮。。。。。。

后来我猜想可能是开发板的内核中已经有了led驱动,我这个led驱动和那个led驱动本质是一样的只是名字不一样那个,应该是冲突了。。。。。

于是卸下屏幕观看,开机,灯全亮,加载我的驱动,灯全灭,欧耶~看来我猜的有道理???????

好了,不说那些桑心的事啦!整理整理发型,明天继续!

等串口线回来了,一定要把这个问题弄清楚,看是不是这样的!如果有遇到这个问题的大牛不妨分享下经验,灰常感谢!

 

驱动编程有感:

1 驱动编程有种瞻前顾后的感觉,在linux系统中的编程,眼光停留在硬件上远远不够的,还要注意linux系统的外部接口,只有这样才能做到外部接口和自己写的驱动接口完美衔接。

2 感觉驱动开发就像带着脚镣跳舞,底层的硬件的基本相关函数,linux已经支持,上层的系统接口,linux系统也已经制定了,我们能做的,就是再硬件和系统之间合理周旋。是一个非常考验人的活!前期感觉是累,后期恐怕就是智商挑战了吧!

3 分析linux驱动的时候,可以适当采用倒叙分析,先从操作系统接口开始,一步一步找到和硬件完美的契合点。。。

 

转载于:https://www.cnblogs.com/heat-man/p/4175842.html

2015-11-27 19:23:08 hulu_arm 阅读数 567
  • 驱动框架入门之LED-linux驱动开发第4部分

    本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入。通过led驱动框架和gpiolib的这两个框架的详细解释,让大家学习内核中驱动框架的使用方法,这个对后续学习基于框架的复杂驱动非常有帮助。

    5851 人正在学习 去看看 朱有鹏

一:开发说明
1:开发led驱动之前必须满足如下条件才能正式开发
FL2440开发板已经正确移植bootload与linux内核(包括根文件系统的移植),也就是说在开发该驱动之前你自己的开发板已经能启动bootload,并由bootload引导linux内核启动(包含根文件系统),在以上条件都满足的情况下,就可以愉快的开发led驱动。

2:本驱动是基于linux-3.0内核开发的,也就是开发板运行的linux内核必须与驱动所用的内核一致,并且内核必须是已经使用交叉编译器编译过的。

二:led驱动代码如下


#include <linux/module.h>   /* Every Linux kernel module must include this head */
#include <linux/init.h>     /* Every Linux kernel module must include this head */
#include <linux/kernel.h>   /* printk() */
#include <linux/fs.h>       /* struct fops */
#include <linux/errno.h>    /* error codes */
#include <linux/cdev.h>     /* cdev_alloc()  */
#include <asm/io.h>         /* ioremap()  */
#include <linux/ioport.h>   /* request_mem_region() */

#include <asm/ioctl.h>      /* Linux kernel space head file for macro _IO() to generate ioctl command  */
#ifndef __KERNEL__
#include <sys/ioctl.h>      /* User space head file for macro _IO() to generate ioctl command */
#endif
//#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */


#define DRV_AUTHOR                "hulu <1334528355@qq.com>
#define DRV_DESC                  "S3C24XX LED driver"

#define DEV_NAME                  "led"
#define LED_NUM                   4

/* Set the LED dev major number */
//#define LED_MAJOR                 79
#ifndef LED_MAJOR
#define LED_MAJOR                 0
#endif

#define DRV_MAJOR_VER             1
#define DRV_MINOR_VER             0
#define DRV_REVER_VER             0

#define DISABLE                   0
#define ENABLE                    1

#define GPIO_INPUT                0x00
#define GPIO_OUTPUT               0x01

//使用魔术转换产生独一无二的命令
define PLATDRV_MAGIC             0x60
#define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)

//fl2440上led使用的寄存器地址:GPBCON 0X56000010,GPBDAT 0X56000014,GPBUP 0X56000018
define S3C_GPB_BASE              0x56000010
#define GPBCON_OFFSET             0
#define GPBDAT_OFFSET             4
#define GPBUP_OFFSET              8
#define S3C_GPB_LEN               0x10        /* 0x56000010~0x56000020  */

int led[LED_NUM] = {5,6,8,10};  /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */

//物理地址映射到虚拟地址变量
static void __iomem *s3c_gpb_membase;

//寄存器读写
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)
#define s3c_gpio_read(reg)       __raw_readl((reg)+s3c_gpb_membase)


int dev_count = ARRAY_SIZE(led);
int dev_major = LED_MAJOR;
int dev_minor = 0;
int debug = DISABLE;

//该结构体是表示字符设备在内核的内部结构
static struct cdev      *led_cdev;

//led硬件初始化,即寄存器初始化
static int s3c_hw_init(void)
{
    int          i;
    volatile unsigned long  gpb_con, gpb_dat, gpb_up;
    //申请一段长为S3C_GPB_LEN的内存
    if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))
    {
        return -EBUSY;
    }
    //物理地址到虚拟地址的映射
     if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )
    {
        release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);
        return -ENOMEM;
    }

    for(i=0; i<dev_count; i++)
    {
        /* Set GPBCON register, set correspond GPIO port as input or output mode  */
        gpb_con = s3c_gpio_read(GPBCON_OFFSET);
        gpb_con &= ~(0x3<<(2*led[i]));   /* Clear the currespond LED GPIO configure register */
        gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */
        s3c_gpio_write(gpb_con, GPBCON_OFFSET);

        /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable  */
        gpb_up = s3c_gpio_read(GPBUP_OFFSET);
        //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */
        gpb_up |= (0x1<<led[i]);  /* Disable pull up resister */
        s3c_gpio_write(gpb_up, GPBUP_OFFSET);

        /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */
        gpb_dat |= (0x1<<led[i]);  /* This port set to high level, then turn LED off */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }

    return 0;
}

//led开关函数
static void turn_led(int which, unsigned int cmd)
{
    volatile unsigned long  gpb_dat;

    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);

    if(LED_ON == cmd)
    {
        gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */
    }
    else if(LED_OFF == cmd)
    {
        gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */
    }

    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}

//驱动卸载反初始化
static void s3c_hw_term(void)
{
    int                     i;
    volatile unsigned long  gpb_dat;

    for(i=0; i<dev_count; i++)
    {
        gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
        gpb_dat |= (0x1<<led[i]);  /* Turn LED off */
        s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
    }
   //释放之前申请的内存
    release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);
    iounmap(s3c_gpb_membase);
}

//应用程序调用系统调用open通过设备文件名找到iNode结构,并且调用该函数
static int led_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);

    file->private_data = (void *)minor;

    printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
    return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
    printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));

    return 0;
}

static void print_help(void)
{
    printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
    //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
    printk("Turn LED on command  : %u\n", LED_ON);
    printk("Turn LED off command : %u\n", LED_OFF);

    return;
}

//led控制函数,打开还是关闭led
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int which = (int)file->private_data;

    switch (cmd)
    {
        case LED_ON:

            turn_led(which, LED_ON);
            break;

        case LED_OFF:
            turn_led(which, LED_OFF);
            break;

        default:
            printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
            print_help();
            break;
    }

    return 0;
}

//将led的各个操作用该结构体联系起来
static struct file_operations led_fops = 
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
};

//安装驱动的时候调用该函数
static int __init s3c_led_init(void)
{
    int                    result;
    dev_t                  devno;

    if( 0 != s3c_hw_init() )
    {
        printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
        return -ENODEV;
    }

    /*  Alloc the device for driver */
    if (0 != dev_major) /*  Static */
    {
        devno = MKDEV(dev_major, 0);
        result = register_chrdev_region (devno, dev_count, DEV_NAME);
    }
    else
    {
        result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
        dev_major = MAJOR(devno);
    }

    /*  Alloc for device major failure */
    if (result < 0)
    {
        printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
        return -ENODEV;
    } 
    printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);

    if(NULL == (led_cdev=cdev_alloc()) )
    {
        printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
        unregister_chrdev_region(devno, dev_count);
        return -ENOMEM;
    }

    led_cdev->owner = THIS_MODULE;
    cdev_init(led_cdev, &led_fops);

    result = cdev_add(led_cdev, devno, dev_count);
    if (0 != result)
    {   
        printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); 
        goto ERROR;
    }


    printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n", 
            DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);
    return 0;


ERROR:
    printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
    cdev_del(led_cdev);
    unregister_chrdev_region(devno, dev_count);
    return result;
}

//卸载驱动默认调用此函数
tatic void __exit s3c_led_exit(void)
{
    dev_t devno = MKDEV(dev_major, dev_minor);

    s3c_hw_term();

    cdev_del(led_cdev);
    unregister_chrdev_region(devno, dev_count);

    printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", 
            DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);

    return ;
}



/* These two functions defined in <linux/init.h> */
module_init(s3c_led_init);
module_exit(s3c_led_exit);

module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO);

MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");

三:编译该驱动使用的Makefile

ARCH = s3c2440
KERNEL_VER = linux-3.0
LINUX_SRC ?= /home/hulu/fl2440/kernel/$(KERNEL_VER)
PWD := $(shell pwd)

obj-m += s3c_led.o

modules:
     @make -C $(LINUX_SRC) M=$(PWD) modules
     @make clear

clear:
      @rm -f *.o *.cmd *.mod.c
      @rm -rf  *~ core .depend  .tmp_versions
      Module.symvers modules.order -f
      @rm -f .*ko.cmd .*.o.cmd .*.o.d

clean: clear
      @rm -f  *.ko

四:基于fl2440加载驱动
由于在该驱动文件中使用的是动态获取主设备号:

动态获取主设备号 
result=alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
  dev_major = MAJOR(devno);

所以在安装驱动之前并不知道内核给我们分配哪一个主设备号,故只有在安装该驱动之后才能在/dev/目录下创建设备节点。因此,为了加载一个使用动态获取主设备号的设备驱动程序,对insmod的调用可替换为一个简单的脚本,该脚本在调用insmod之后读取/proc/devices以获得新分配的主设备号,然后创建对应的设备文件。
该脚本文件如下:

  #!/bin/sh
  insmod s3c_ied.ko

  major=`cat /proc/devices | grep led | awk '{print $1}'`

  //创建设备节点
  mknod -m 755 /dev/led0 c $major 0

  mknod -m 755 /dev/led1 c $major 1

  mknod -m 755 /dev/led2 c $major 2

  mknod -m 755 /dev/led3 c $major 3

驱动安装成功如下:

这里写图片描述

五:编写测试文件led_test.c
vim led_test.c

#include <stdio.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>

#define buffer_num                20
#define PLATDRV_MAGIC             0x60
#define LED_ON                    _IO(PLATDRV_MAGIC, 0x19)
#define LED_OFF                   _IO(PLATDRV_MAGIC, 0x18)
int main(void)
{
         int  j,i;
         int   fd;
         char  buffer[buffer_num];

         for(i=0;i<4;i++)
          {
            snprintf(buffer,sizeof(buffer),"/dev/led%d",i);
              fd=open(buffer,O_RDWR);
              if(fd<0)
              {    printf("open error");
                   return -1;
              }
              ioctl(fd,LED_ON);

              close(fd);
          }
       return  0;
}

然后用自己的交叉编译器arm-linux-gcc编译该c文件会得到一个led的a.out文件,使用tftp服务器把它送到开发板,然后使用chmod 755 a.out (解权限)
在开发板下执行./a.out即可

这样就可以在自己的开发扳上看到四个led灯同时亮,哈哈!以上的代码编写折腾我好久,虽然已经编写成功,但可能还存在不足之处,欢迎各位指点,同时大家有什么疑问直接留言给我,咱们一起探讨!

没有更多推荐了,返回首页