字符设备驱动和平台设备驱动对比

gpio 字符设备驱动

#define GPIO_OFT(x) ((x) - 0x56000000)
#define GPFCON  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))


/* 应用程序对设备文件/dev/leds执行open(...)时,
 * 就会调用s3c24xx_leds_open函数
 */
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
	int minor = MINOR(inode->i_rdev); //MINOR(inode->i_cdev);

	switch(minor)
	{
        case 0: /* /dev/leds */
        {
            // 配置3引脚为输出
            //s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
            GPFCON &= ~(0x3<<(4*2));
            GPFCON |= (1<<(4*2));
            
            //s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
            GPFCON &= ~(0x3<<(5*2));
            GPFCON |= (1<<(5*2));

            //s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
            GPFCON &= ~(0x3<<(6*2));
            GPFCON |= (1<<(6*2));

            // 都输出0
            //s3c2410_gpio_setpin(S3C2410_GPF4, 0);
            GPFDAT &= ~(1<<4);
            
            //s3c2410_gpio_setpin(S3C2410_GPF5, 0);
            GPFDAT &= ~(1<<5);
            //s3c2410_gpio_setpin(S3C2410_GPF6, 0);
            GPFDAT &= ~(1<<6);

            down(&leds_lock);
            leds_status = 0x0;
            up(&leds_lock);
                
            break;
        }

        case 1: /* /dev/led1 */
        {
            s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
            s3c2410_gpio_setpin(S3C2410_GPF4, 0);
            
            down(&leds_lock);
            leds_status &= ~(1<<0);
            up(&leds_lock);
            
            break;
        }

        case 2: /* /dev/led2 */
        {
            s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
            s3c2410_gpio_setpin(S3C2410_GPF5, 0);
            leds_status &= ~(1<<1);
            break;
        }

        case 3: /* /dev/led3 */
        {
            s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
            s3c2410_gpio_setpin(S3C2410_GPF6, 0);

            down(&leds_lock);
            leds_status &= ~(1<<2);
            up(&leds_lock);
            
            break;
        }
        
	}
	
    return 0;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations s3c24xx_leds_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   s3c24xx_leds_open,     
	.read	=	s3c24xx_leds_read,	   
	.write	=	s3c24xx_leds_write,	   
};

/*
 * 执行insmod命令时就会调用这个函数 
 */
static int __init s3c24xx_leds_init(void)
//static int __init init_module(void)

{
    int ret;
	int minor = 0;

    gpio_va = ioremap(0x56000000, 0x100000);
	if (!gpio_va) {
		return -EIO;
	}

    /* 注册字符设备
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */
    ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }

	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
    


	leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds"); /* /dev/leds */
	
	for (minor = 1; minor < 4; minor++)  /* /dev/led1,2,3 */
	{
		leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
		if (unlikely(IS_ERR(leds_class_devs[minor])))
			return PTR_ERR(leds_class_devs[minor]);
	}
        
    printk(DEVICE_NAME " initialized\n");
    return 0;
}

/*
 * 执行rmmod命令时就会调用这个函数 
 */
static void __exit s3c24xx_leds_exit(void)
{
	int minor;
    /* 卸载驱动程序 */
    unregister_chrdev(LED_MAJOR, DEVICE_NAME);

	for (minor = 0; minor < 4; minor++)
	{
		class_device_unregister(leds_class_devs[minor]);
	}
	class_destroy(leds_class);
    iounmap(gpio_va);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);

应用程序和vfs之间的接口是系统调用,而vfs与文件系统以及设备文件之间的接口是file_operations成员
函数。
由于字符设备没有类似磁盘的,ext,fat等文件系统(和vfs进行对接),
所以就直接由设备驱动提供了。file_operatitions是字符设备驱动的核心。

gpio 平台设备驱动

linux 驱动模型的作用:

  1. 设备驱动模型实现uevent机制,调用应用层的medv来自动创建设备文件
  2. 设备驱动模型通过sysfs文件系统向用户层提供设备驱动视图
  3. 设备驱动模型提供统一的电源管理机制
  4. 设备驱动模型提供各种对象实例的引用计数,防止对象被应用层误删。设备模型的所有数据结构均是继承kobject而来,而kobject就提供基础的计数功能
  5. 设备驱动模型提供多一种方式给应用层,用户和内核可以通过sysfs进行交互,如通过修改/sys目录下设备的文件内容,即可以直接修改设备对应的参数。
static int led_open(struct inode *inode, struct file *file)
{
	//printk("first_drv_open\n");
	/* 配置为输出 */
	*gpio_con &= ~(0x3<<(pin*2));
	*gpio_con |= (0x1<<(pin*2));
	return 0;	
}
static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   led_open,     
	.write	=	led_write,	   
};

static int led_probe(struct platform_device *pdev)
{
	struct resource		*res;

	/* 根据platform_device的资源进行ioremap */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	pin = res->start;

	/* 注册字符设备驱动程序 */

	printk("led_probe, found led\n");

	major = register_chrdev(0, "myled", &led_fops);

	cls = class_create(THIS_MODULE, "myled");

	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
	
	return 0;
}

static int led_remove(struct platform_device *pdev)
{
	/* 卸载字符设备驱动程序 */
	/* iounmap */
	printk("led_remove, remove led\n");

	class_device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);
	
	return 0;
}


struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",
	}
};


static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

可以看出,如果使用平台设备驱动的方式去实现的话,就不必将,设备寄存器的信息 再驱动代码里面写死,而是通过设备树,或者板级信息,拿到设备寄存器相关的信息。实现设备和驱动的分离。不过这个例子并没有使用gpio子系统,只是再字符设备驱动之上套了一层壳而已。所有仍需要手动调用,class_device_create,去创建/dev ,/sys路径下的设备文件。以便用户可以访问该驱动。

gpio 子系统驱动代码

 51 struct pl061_gpio {                                                                                          
 52     spinlock_t      lock;
 53 
 54     void __iomem        *base;
 55     struct gpio_chip    gc;//关键结构体,还是gpio_chip
 56 
 57 #ifdef CONFIG_PM
 58     struct pl061_context_save_regs csave_regs;
 59 #endif
 60 }
 
236 static int pl061_probe(struct amba_device *adev, const struct amba_id *id)                                   
237 {
238     struct device *dev = &adev->dev;
239     struct pl061_platform_data *pdata = dev_get_platdata(dev);
240     struct pl061_gpio *chip;
241     int ret, irq, i, irq_base;
242 
243     chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
244     if (chip == NULL)
245         return -ENOMEM;
246 
247     if (pdata) {
248         chip->gc.base = pdata->gpio_base;
249         irq_base = pdata->irq_base;
250         if (irq_base <= 0) {
251             dev_err(&adev->dev, "invalid IRQ base in pdata\n");
252             return -ENODEV;
253         }
254     } else {
                                                                                               236,1         57%
253         }                                                                                                    
254     } else {
255         if (dev->of_node) {
256             i = of_alias_get_id(dev->of_node, "gpio");
257             chip->gc.base = i * PL061_GPIO_NR;
258         }
259 
260         if (chip->gc.base < 0)
261             chip->gc.base = -1;
262         irq_base = 0;
263     }
264 
265     chip->base = devm_ioremap_resource(dev, &adev->res);
266     if (IS_ERR(chip->base))
267         return PTR_ERR(chip->base);
268 
269     spin_lock_init(&chip->lock);
270 
271     if (of_property_read_bool(dev->of_node, "gpio-ranges")){
272     chip->gc.request = pl061_gpio_request;
273     chip->gc.free = pl061_gpio_free;
274     }
275     chip->gc.direction_input = pl061_direction_input;
276     chip->gc.direction_output = pl061_direction_output;
277     chip->gc.get = pl061_get_value;
278     chip->gc.set = pl061_set_value;
279     chip->gc.ngpio = PL061_GPIO_NR;
280     chip->gc.label = dev_name(dev);
281     chip->gc.dev = dev;
282     chip->gc.owner = THIS_MODULE;
283 
284     ret = gpiochip_add(&chip->gc);//将填充好的结构体,注册进gpio子系统
285     if (ret)
286         return ret;
...
}

398 static struct amba_driver pl061_gpio_driver = {
399     .drv = {
400         .name   = "pl061_gpio",
401 #ifdef CONFIG_PM
402         .pm = &pl061_dev_pm_ops,
403 #endif
404     },
405     .id_table   = pl061_ids,
406     .probe      = pl061_probe,
407 };
408 
409 static int __init pl061_gpio_init(void)
410 {
411     return amba_driver_register(&pl061_gpio_driver);
412 }
413 module_init(pl061_gpio_init);

使用Linux自带的gpio子系统驱动框架的,就不用再手动,创建/dev目录下的设备文件。因为驱动框架会为用户自动做好。

gpio子系统介绍文档:
https://xuesong.blog.csdn.net/article/details/108909011

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个关于在 Ubuntu 22.04.2 LTS 系统下使用 C 语言编写内核模块文件 chardev.c 和 Makefile 文件,实现简单的字符设备驱动程序的问题。这个问题比较复杂,需要一定的系统编程和驱动开发经验。下面是一些大致的步骤和代码示例,供您参考。 首先,我们需要编写 chardev.c 文件,这是一个字符设备驱动程序的核心代码。下面是一个示例: ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #define DEVICE_NAME "chardev" // 设备名 #define BUF_LEN 80 // 缓冲区大小 MODULE_LICENSE("GPL"); // 注册模块 static int Major; // 主设备号 static int Device_Open = 0; // 设备是否打开 static char msg[BUF_LEN]; // 缓冲区 static char *msg_Ptr; static int device_open(struct inode *inode, struct file *file) { if (Device_Open) return -EBUSY; Device_Open++; msg_Ptr = msg; try_module_get(THIS_MODULE); return 0; } static int device_release(struct inode *inode, struct file *file) { Device_Open--; module_put(THIS_MODULE); return 0; } static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t * offset) { int bytes_read = 0; if (*msg_Ptr == 0) return 0; while (length && *msg_Ptr) { put_user(*(msg_Ptr++), buffer++); length--; bytes_read++; } return bytes_read; } static ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t * off) { int i; for (i = 0; i < len && i < BUF_LEN; i++) get_user(msg[i], buff + i); msg_Ptr = msg; return i; } static long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) { switch (ioctl_num) { case 1: printk(KERN_INFO "ioctl 1 invoked\n"); break; case 2: printk(KERN_INFO "ioctl 2 invoked\n"); break; default: return -EINVAL; } return 0; } static struct file_operations fops = { .read = device_read, .write = device_write, .unlocked_ioctl = device_ioctl, .open = device_open, .release = device_release }; static int __init chardev_init(void) { Major = register_chrdev(0, DEVICE_NAME, &fops); if (Major < 0) { printk(KERN_ALERT "Registering char device failed with %d\n", Major); return Major; } printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major); printk(KERN_INFO "the driver, create a dev file with\n"); printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major); return 0; } static void __exit chardev_exit(void) { unregister_chrdev(Major, DEVICE_NAME); printk(KERN_INFO "Goodbye, World!\n"); } module_init(chardev_init); module_exit(chardev_exit); ``` 上面的代码实现了设备驱动程序的基本功能,包括打开、读、写、I/O 控制和关闭等操作。其中,device_open() 函数用于打开设备device_release() 函数用于关闭设备device_read() 函数用于读取设备数据,device_write() 函数用于写入设备数据,device_ioctl() 函数用于执行 I/O 控制操作。 接下来,我们需要编写 Makefile 文件,用于编译 chardev.c 文件。下面是一个示例: ``` obj-m := chardev.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean ``` 上面的 Makefile 文件定义了一个 obj-m 变量,用于指定要编译的目标模块。然后,使用 $(KERNELDIR) 变量指定内核源代码目录,并使用 $(PWD) 变量指定当前目录。最后,使用 make 命令编译模块。 最后,我们需要编写一个测试程序 test.c,用于测试 chardev 模块。下面是一个示例: ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define DEVICE "/dev/chardev" // 设备文件名 #define IOCTL_NUM1 1 // I/O 控制命令 1 #define IOCTL_NUM2 2 // I/O 控制命令 2 int main() { int fd, ret; char buf[80]; fd = open(DEVICE, O_RDWR); // 打开设备 if (fd < 0) { perror("Failed to open device\n"); exit(1); } ret = read(fd, buf, sizeof(buf)); // 读取设备数据 if (ret < 0) { perror("Failed to read from device\n"); exit(1); } printf("Read from device: %s\n", buf); ret = write(fd, "Hello, world!", 13); // 写入设备数据 if (ret < 0) { perror("Failed to write to device\n"); exit(1); } printf("Write to device: %d bytes\n", ret); ret = ioctl(fd, IOCTL_NUM1); // 执行 I/O 控制命令 1 if (ret < 0) { perror("Failed to invoke ioctl 1\n"); exit(1); } ret = ioctl(fd, IOCTL_NUM2); // 执行 I/O 控制命令 2 if (ret < 0) { perror("Failed to invoke ioctl 2\n"); exit(1); } close(fd); // 关闭设备 return 0; } ``` 上面的代码使用 open() 函数打开设备文件,然后使用 read() 函数读取设备数据,使用 write() 函数写入设备数据,使用 ioctl() 函数执行 I/O 控制操作,最后使用 close() 函数关闭设备文件。 最后,我们可以使用 gcc 命令编译 chardev.c 和 test.c 文件,例如: ``` gcc -o chardev chardev.c gcc -o test test.c ``` 然后,我们可以执行 test 程序来测试 chardev 模块,例如: ``` ./test ``` 这样,我们就可以在 Ubuntu 22.04.2 LTS 系统下使用 C 语言编写内核模块文件 chardev.c 和 Makefile 文件,实现简单的字符设备驱动程序,并编写测试程序 test.c,访问创建的字符设备文件,并使用 gcc 编译这个字符设备文件然后运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值