linux驱动(二):led补

2024-01-03 08:45:15

? ? ? ? 本文主要探讨s5pv210的led驱动相关知识,包括驱动主次设备注册和取消,udev(mdev)机制,静态和动态映射操作寄存器。

字符设备驱动注册
????????
老接口(register_chrdev)

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{
?? ?return __register_chrdev(major, 0, 256, name, fops);
}

????????新接口(register_chrdev_region/alloc_chrdev_region)

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
?? ?struct char_device_struct *cd;
?? ?dev_t to = from + count;
?? ?dev_t n, next;

?? ?for (n = from; n < to; n = next) {
?? ??? ?next = MKDEV(MAJOR(n)+1, 0);
?? ??? ?if (next > to)
?? ??? ??? ?next = to;
?? ??? ?cd = __register_chrdev_region(MAJOR(n), MINOR(n),
?? ??? ??? ? ? ? ? next - n, name);
?? ??? ?if (IS_ERR(cd))
?? ??? ??? ?goto fail;
?? ?}
?? ?return 0;
fail:
?? ?to = n;
?? ?for (n = from; n < to; n = next) {
?? ??? ?next = MKDEV(MAJOR(n)+1, 0);
?? ??? ?kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
?? ?}
?? ?return PTR_ERR(cd);
}

????????指定主次设备号(/proc/devices查看未使用驱动号)
????????form:起始设备号(static dev_t dev;)
????????count:次设备号数量
????????name:驱动名字

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
?? ??? ??? ?const char *name)
{
?? ?struct char_device_struct *cd;
?? ?cd = __register_chrdev_region(0, baseminor, count, name);
?? ?if (IS_ERR(cd))
?? ??? ?return PTR_ERR(cd);
?? ?*dev = MKDEV(cd->major, cd->baseminor);
?? ?return 0;
}

????????内核分配主次设备号(从254开始查找未使用驱动号)
????????dev:内核分配的主次设备号(static dev_t dev;)
????????baseminor:次设备号起始号
????????count:次设备号个数
????????name:驱动名字

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
?? ??? ??? ? ? int minorct, const char *name)
{
?? ?struct char_device_struct *cd, **cp;
?? ?int ret = 0;
?? ?int i;

?? ?cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
?? ?if (cd == NULL)
?? ??? ?return ERR_PTR(-ENOMEM);

?? ?mutex_lock(&chrdevs_lock);

?? ?/* temporary */
?? ?if (major == 0) {
?? ??? ?for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
?? ??? ??? ?if (chrdevs[i] == NULL)
?? ??? ??? ??? ?break;
?? ??? ?}

?? ??? ?if (i == 0) {
?? ??? ??? ?ret = -EBUSY;
?? ??? ??? ?goto out;
?? ??? ?}
?? ??? ?major = i;
?? ??? ?ret = major;
?? ?}

?? ?cd->major = major;
?? ?cd->baseminor = baseminor;
?? ?cd->minorct = minorct;
?? ?strlcpy(cd->name, name, sizeof(cd->name));

?? ?i = major_to_index(major);

?? ?for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
?? ??? ?if ((*cp)->major > major ||
?? ??? ? ? ?((*cp)->major == major &&
?? ??? ? ? ? (((*cp)->baseminor >= baseminor) ||
?? ??? ? ? ? ?((*cp)->baseminor + (*cp)->minorct > baseminor))))
?? ??? ??? ?break;

?? ?/* Check for overlapping minor ranges. ?*/
?? ?if (*cp && (*cp)->major == major) {
?? ??? ?int old_min = (*cp)->baseminor;
?? ??? ?int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
?? ??? ?int new_min = baseminor;
?? ??? ?int new_max = baseminor + minorct - 1;

?? ??? ?/* New driver overlaps from the left. ?*/
?? ??? ?if (new_max >= old_min && new_max <= old_max) {
?? ??? ??? ?ret = -EBUSY;
?? ??? ??? ?goto out;
?? ??? ?}

?? ??? ?/* New driver overlaps from the right. ?*/
?? ??? ?if (new_min <= old_max && new_min >= old_min) {
?? ??? ??? ?ret = -EBUSY;
?? ??? ??? ?goto out;
?? ??? ?}
?? ?}

?? ?cd->next = *cp;
?? ?*cp = cd;
?? ?mutex_unlock(&chrdevs_lock);
?? ?return cd;
out:
?? ?mutex_unlock(&chrdevs_lock);
?? ?kfree(cd);
?? ?return ERR_PTR(ret);
}

????????注册都是用该函数,判断major为0则自动分配设备号,且从254依次递减查找未使用的号码

void unregister_chrdev_region(dev_t from, unsigned count)
{
?? ?dev_t to = from + count;
?? ?dev_t n, next;

?? ?for (n = from; n < to; n = next) {
?? ??? ?next = MKDEV(MAJOR(n)+1, 0);
?? ??? ?if (next > to)
?? ??? ??? ?next = to;
?? ??? ?kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
?? ?}
}

????????注销驱动
????????form:驱动号
????????count:驱动个数

cdev结构体(字符设备描述,cdev.h)

struct cdev {
?? ?struct kobject kobj; /* 内嵌的 kobject 对象 */
?? ?struct module *owner; /*所属模块*/ ?
?? ?const struct file_operations *ops; /*文件操作结构体*/
?? ?struct list_head list; /*内核链表*/
?? ?dev_t dev;/*设备号*/
?? ?unsigned int count;/*设备个数*/
};

????????主设备号MAJOR(dev_t dev)?
????????次设备号MINOR(dev_t dev)?
????????生成设备号MKDEV(int major, int minor)
????????cdev中dev_t dev定义设备号(高12位为主设备号,低20 位为次设备号)
????????MAJOR和MINOR根据已实例化的cdev中的dev_t dev获得主次设备号
????????MKDEV实例化cdev中的dev_t dev设备号

cdev结构体操作函数
?

struct cdev *cdev_alloc(void)
{
?? ?struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
?? ?if (p) {
?? ??? ?INIT_LIST_HEAD(&p->list);
?? ??? ?kobject_init(&p->kobj, &ktype_cdev_dynamic);
?? ?}
?? ?return p;
}


????????动态申请cdev内存
?

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
?? ?memset(cdev, 0, sizeof *cdev);
?? ?INIT_LIST_HEAD(&cdev->list);
?? ?kobject_init(&cdev->kobj, &ktype_cdev_default);
?? ?cdev->ops = fops;
} ??


????????初始化cdev中的file_operation

?int cdev_add(struct cdev *p, dev_t dev, unsigned count) ?

?????????注册设备,通常发生在驱动模块的加载函数中

void cdev_put(struct cdev *p);

??????????释放动态申请cdev内存

void cdev_del(struct cdev *p);

????????注销设备

????????驱动注册和取消流程:register_chrdev_region/alloc_chrdev_region + cdev_alloc + cdev_init + cdev_add + cdev_put + cdev_del + unregister_chrdev_region

udev(mdev)
????????
udev是应用程序,驱动注册和注销信息传给udev,udev在应用层进行设备文件创建和删除
????????自动创建/sys/class/设备类/设备目录
????????/etc/init.d/rcS有关于udev的配置

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

????????mdev -s:系统启动时扫描/sys/class和/sys/block查找dev文件
? ? ? ? ex:/sys/class/tty/tty0/dev内容为4:0,即主设备号是4,次设备号是0,dev上级目录为设备名(tty0),/sys/class/下每个文件夹代表一个子系统
echo /sbin/mdev > /proc/sys/kernel/hotplug:有热插拔时内核调用/proc/sys/kernel/hotplug文件里指定应用程序来处理????????

????????创建驱动类

#define class_create(owner, name)?? ??? ?\
({?? ??? ??? ??? ??? ??? ?\
?? ?static struct lock_class_key __key;?? ?\
?? ?__class_create(owner, name, &__key);?? ?\
})


struct class *__class_create(struct module *owner, const char *name,
?? ??? ??? ? ? ? struct lock_class_key *key)
{
?? ?struct class *cls;
?? ?int retval;

?? ?cls = kzalloc(sizeof(*cls), GFP_KERNEL);
?? ?if (!cls) {
?? ??? ?retval = -ENOMEM;
?? ??? ?goto error;
?? ?}

?? ?cls->name = name;
?? ?cls->owner = owner;
?? ?cls->class_release = class_create_release;

?? ?retval = __class_register(cls, key);
?? ?if (retval)
?? ??? ?goto error;

?? ?return cls;

error:
?? ?kfree(cls);
?? ?return ERR_PTR(retval);
}

????????owner:THIS_MODULE(通常)
????????name:类名
????????返回值class类指针????????

????????驱动类注销

void class_destroy(struct class *cls)
{
?? ?if ((cls == NULL) || (IS_ERR(cls)))
?? ??? ?return;

?? ?class_unregister(cls);
}

????????cls:创建的驱动类

????????

????????创建设备

struct device *device_create(struct class *class, struct device *parent,
?? ??? ??? ? ? ? dev_t devt, void *drvdata, const char *fmt, ...)
{
?? ?va_list vargs;
?? ?struct device *dev;

?? ?va_start(vargs, fmt);
?? ?dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
?? ?va_end(vargs);
?? ?return dev;
}

????????class:类设备
????????parent:父设备,一般为NULL
????????devt:设备号
????????drvdata:设备数据
????????fmt:设备名字


????????注销设备

void device_destroy(struct class *class, dev_t devt)
{
?? ?struct device *dev;

?? ?dev = class_find_device(class, NULL, &devt, __match_devt);
?? ?if (dev) {
?? ??? ?put_device(dev);
?? ??? ?device_unregister(dev);
?? ?}
}

????????class:类设备
????????devt:设备号

? ? ? ?
????????udev机制流程:kernel(class_create,class_destroy,device_create,device_destroy)-->udev(mdev,应用层)-->/sys/class/设备类/设备?

? ? ? ? ?函数嵌套:class_create -->__class_create-->__class_register-->kset_register-->kobject_uevent
????????函数嵌套:device_create-->device_create_vargs-->kobject_set_name_vargs-->device_register-->device_add-->kobject_add-->kobject_uevent? ??? ??? ??? ??? ?

静态映射表建立

/* minimal IO mapping */

static struct map_desc s5p_iodesc[] __initdata = {
?? ?{
?? ??? ?.virtual?? ?= (unsigned long)S5P_VA_CHIPID,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S5P_PA_CHIPID),
?? ??? ?.length?? ??? ?= SZ_4K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?}, {
?? ??? ?.virtual?? ?= (unsigned long)S3C_VA_SYS,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S5P_PA_SYSCON),
?? ??? ?.length?? ??? ?= SZ_64K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?}, {
?? ??? ?.virtual?? ?= (unsigned long)S3C_VA_UART,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S3C_PA_UART),
?? ??? ?.length?? ??? ?= SZ_4K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?}, {
?? ??? ?.virtual?? ?= (unsigned long)VA_VIC0,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S5P_PA_VIC0),
?? ??? ?.length?? ??? ?= SZ_16K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?}, {
?? ??? ?.virtual?? ?= (unsigned long)VA_VIC1,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S5P_PA_VIC1),
?? ??? ?.length?? ??? ?= SZ_16K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?}, {
?? ??? ?.virtual?? ?= (unsigned long)S3C_VA_TIMER,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S5P_PA_TIMER),
?? ??? ?.length?? ??? ?= SZ_16K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?}, {
?? ??? ?.virtual?? ?= (unsigned long)S5P_VA_GPIO,
?? ??? ?.pfn?? ??? ?= __phys_to_pfn(S5P_PA_GPIO),
?? ??? ?.length?? ??? ?= SZ_4K,
?? ??? ?.type?? ??? ?= MT_DEVICE,
?? ?},
};

????????建立映射表的三个关键:物理地址和虚拟地址相关宏定义,映射表建立函数
????????映射表建立函数:smdkc110_map_io(mach-smdkc110.c)-->s5p_init_io-->iotable_init
????????s5p_iodesc(cpu.c)是结构体数组数组中每个元素为一段段物理地址到虚拟地址映射,iotable_init函数将结构体数组格式表建立成MMU页表映射关系
????????开机-->start_kernel-->setup_arch-->paging_init-->devicemaps_init:mdesc->map_io();
?? ??? ??? ?

内核寄存器读写接口

include/asm/io.h
#define writeb(v,c)?? ??? ?({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c)?? ??? ?({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c)?? ??? ?({ __iowmb(); writel_relaxed(v,c); })
#define writesb(p,d,l)?? ??? ?__raw_writesb(__mem_pci(p),d,l)
#define writesw(p,d,l)?? ??? ?__raw_writesw(__mem_pci(p),d,l)
#define writesl(p,d,l)?? ??? ?__raw_writesl(__mem_pci(p),d,l)

#define readb(c)?? ??? ?({ u8 ?__v = readb_relaxed(c); __iormb(); __v; })
#define readw(c)?? ??? ?({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c)?? ??? ?({ u32 __v = readl_relaxed(c); __iormb(); __v; })
#define readsb(p,d,l)?? ??? ?__raw_readsb(__mem_pci(p),d,l)
#define readsw(p,d,l)?? ??? ?__raw_readsw(__mem_pci(p),d,l)
#define readsl(p,d,l)?? ??? ?__raw_readsl(__mem_pci(p),d,l)

v为值,c为地址,b为位,w为字节,l为4字节操

? ? demo:

? ? ? ? 动态(静态)注册驱动

? ? ? ? mdev机制

????????动态映射结构体方式操作寄存器

???????内核寄存器读写接口???

led.c??

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

#define FILE    "/dev/led"

char buf[16];
char led_cmd[3][4];

void div_cmd(char *str)
{
        unsigned int m = 0;
        unsigned int n = 0;
        memset(led_cmd,'\0',sizeof(led_cmd));
 
        while(*str != '\0')
        {
                if(*str != ' ')
                {
                        led_cmd[m][n] = *str;
                        n++;
                }
                else
                {
                        led_cmd[m][n] = '\0';
                        n = 0;
                        m++;
                }
                str++;
        }
}

void write_buf()
{
        int i;

        memset(buf,'\0',sizeof(buf));
        if(!strcmp(led_cmd[1],"on"))
                buf[0] = '0';
        if(!strcmp(led_cmd[1],"off"))
                buf[0] = '1';
        buf[1] = *(led_cmd[2]);
} 

int main()
{
        int fd = -1;
        char cmd[16];
        fd = open(FILE, O_RDWR);
        if (fd < 0)
        {
                printf("open %s error.\n", FILE);
                return -1;
        }

        printf("input led cmd :led on|off 1|2|3\n");
        while(1)
        {
                memset(cmd,'\0',sizeof(cmd));
                printf(">>>>");
                fgets(cmd,16,stdin);
                if(cmd[0] == '.' && cmd[1] == 'q')
                {
                        break;
                }
                div_cmd(cmd);
                write_buf();
                write(fd,buf,strlen(buf));
        }
        close(fd);

        return 0;
}

led_dynamic_module.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#include <linux/device.h>

dev_t led_dev;
static struct cdev *led_cdev;
static struct class *led_class;
static struct device *led_device;

//#define MAJOR 227 /*定义主设备号*/
#define BAEMINOR 0
#define DEV_COUNT 1
#define NAME    "led"

#define GPJ0_BASE_PA 0xe0200240

/*
*动态映射结构体方式操作寄存器
typedef struct GPJ0REG
{
        volatile unsigned int gpj0con;
        volatile unsigned int gpj0dat;
} gpj0_reg_t;

gpj0_reg_t *pGPJ0_BASE;
*/

//内核io接口读写寄存器
unsigned int led_tmp;
#define S5P_GPJ0REG(x)          (x)
#define S5P_GPJ0CON                     S5P_GPJ0REG(0)
#define S5P_GPJ0DAT                     S5P_GPJ0REG(4)
static void __iomem *baseaddr;

char kbuf[16];

static ssize_t led_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO,"open led module\n");
        return 0;
}


ssize_t led_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
        int ret = -1;
        copy_to_user(buf,kbuf,size);
        if(ret)
        {
                printk(KERN_ERR,"copy to user error\n");
                return ret;
        }
        printk(KERN_INFO,"copy to user success\n");
        return 0;
}


static ssize_t led_write(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos)
{
        int ret = -1;
        int led;
        memset(kbuf, 0, sizeof(kbuf));
        ret = copy_from_user(kbuf,ubuf,count);
        if(ret)
        {
                printk(KERN_ERR,"copy from user fail\n");
                return ret;
        }

        led = kbuf[1] - '0' + 2;
 
        if (kbuf[0] == '0')
        {
                //pGPJ0_BASE->gpj0con = 0x1111111;
                //pGPJ0_BASE->gpj0dat &= ~(1<<led);
                led_tmp &= ~(1<<led);
                writel(led_tmp,baseaddr + S5P_GPJ0DAT);
        }
        else if (kbuf[0] == '1')
        {
                //pGPJ0_BASE->gpj0con = 0x1111111;
                //pGPJ0_BASE->gpj0dat |= (1<<led);
                led_tmp |= (1<<led);
                writel(led_tmp,baseaddr + S5P_GPJ0DAT);
        }
        printk(KERN_INFO,"copy from user success\n");
        return 0;
}

static int led_close(struct inode *inode, struct file *file)
{
        //pGPJ0_BASE->gpj0dat = (1<<3)|(1<<4)|(1<<5);
        led_tmp = (1<<3)|(1<<4)|(1<<5);
        writel(led_tmp,baseaddr + S5P_GPJ0DAT);
        printk(KERN_INFO,"close led module\n");
        return 0;
}

static const struct file_operations led_fops = {

        .owner = THIS_MODULE,
        .open = led_open,
        .read = led_read,
        .write = led_write,
        .release = led_close
};

static int __init led_char_dev_init()
{
        int ret = -1;
        //MKDEV(MAJOR, 0);      /*定义主次设备号(dev_t led_dev)*/
        //ret = register_chrdev_region(led_dev,DEV_COUNT,NAME);         /*指定设备号注册方式*/
         ret = alloc_chrdev_region(&led_dev,BAEMINOR,DEV_COUNT,NAME);
        if(ret < 0)
        {
                printk(KERN_ERR,"register led device fail\n");
                goto flag_1;
        }

        led_cdev = cdev_alloc();
        if(led_cdev == NULL)
        {
                printk(KERN_ERR,"apply cdev memory fail\n");
                goto flag_2;
        }

        cdev_init(led_cdev,&led_fops);
        //类同cdev_init
        //led_cdev->owner = THIS_MODULE;
        //led_cdev->ops = &led_fops;
        ret = cdev_add(led_cdev,led_dev,DEV_COUNT);
        if(ret)
        {
                printk(KERN_ERR,"device num bind fail\n");
                goto flag_3;
        }
        printk(KERN_INFO,"device num bind sucess\n");

        led_class = class_create(THIS_MODULE,"led_class");
        if(IS_ERR(led_class))
        {
                printk(KERN_ERR,"led class create fail\n");
                goto flag_4;
        }
        printk(KERN_INFO,"led class create success\n");

        led_device = device_create(led_class,NULL,led_dev,NULL,"led");
        if(led_device == NULL)
        {
                printk(KERN_ERR,"device num bind fail\n");
                goto flag_5;
        }
        printk(KERN_INFO,"device num bind sucess\n");

        if(!request_mem_region(GPJ0_BASE_PA,8,"GPJ0_BASE"))
                goto flag_6;

                //pGPJ0_BASE = ioremap(GPJ0_BASE_PA,sizeof(gpj0_reg_t));
                //pGPJ0_BASE->gpj0con = 0x1111111;

                baseaddr = ioremap(GPJ0_BASE_PA, 8);
                writel(0x11111111,baseaddr + S5P_GPJ0CON);
                led_tmp = (1<<3)|(1<<4)|(1<<5);
                writel(led_tmp,baseaddr + S5P_GPJ0DAT);
                return 0;

        flag_6:
                device_destroy(led_class,led_dev);
        flag_5:
                class_destroy(led_class);
        flag_4:
                //cdev_put(led_cdev);
        flag_3:
                cdev_del(led_cdev);
        flag_2:
                unregister_chrdev_region(led_dev,DEV_COUNT);
        flag_1:
                return -EINVAL;
}


static void __exit led_char_dev_exit()
{
        //pGPJ0_BASE->gpj0dat = (1<<3)|(1<<4)|(1<<5);
        //iounmap(pGPJ0_BASE);
        //release_mem_region(GPJ0_BASE_PA,sizeof(gpj0_reg_t));

        led_tmp = (1<<3)|(1<<4)|(1<<5);
        writel(led_tmp,baseaddr + S5P_GPJ0DAT);
        iounmap(baseaddr);
        release_mem_region(GPJ0_BASE_PA,8);


        device_destroy(led_class,led_dev);
        class_destroy(led_class);
        //cdev_put(led_cdev);
        cdev_del(led_cdev);

        unregister_chrdev_region(led_dev,DEV_COUNT);
}

module_init(led_char_dev_init);
module_exit(led_char_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cxb");
MODULE_DESCRIPTION("led dynamic moudle");
MODULE_ALIAS("led");

Makefile

KERN_DIR = /root/kernel
 
obj-m   += led_dynamic_module.o
 
all:
        make -C $(KERN_DIR) M=`pwd` modules 
        arm-linux-gcc led.c -o led
 
cp:
        cp *.ko /root/rootfs/driver
        cp led /root/rootfs/driver
 
 
.PHONY: clean
clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf ./led

结果示例:

文章来源:https://blog.csdn.net/tyro_wzp/article/details/135353122
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。