Linux下字符设备驱动框架

01_无硬件:

四个部分:头文件、两个入口函数初始化、注册入口函数、模块信息描述

//1.头文件
#include <linux/init.h>
#include <linux/module.h>

static dev_t no;
static unsigned count = 1;
static const char* name = "mydev";

static struct cdev mydev;
s
int myopen(struct inode *pi,struct file *pf)
{
    return 0;
}
static const struct file_operations fops={
    .open = myopen,
};
//2.函数init exit
static int myinit(void)
{
    //向上: 内核相关
    //1.注册
    no = MKDEV(MA,MI);
    ret = register_chrdev_region(no,count,name);
    if(0!=ret)
        return ret;
    //2.初始化
    cdev_init(&mydev,&fops);
    //3.添加
    ret = cdev_add(&mydev,no,count);
    if(0!=ret)
    {
         unregister_chrdev_region(no,count);
         return ret;
    } 
     return 0;
}
static void myexit(void)
{
    //删除设备
    cdev_del(&mycdev);
    //解除注册
    unregister_chrdev_region(no,count);
    return;
}
//3.注册入口函数
module_init(myinit)
module_exit(myexit);
//4.模块信息描述
MODULE_LICENSE("GPL");

有一些头文件没加,可以通过sourceinsight搜索函数查看头文件地址。

02_含硬件

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kdev_t.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include"cmd.h"
#include<asm/io.h>
#define MA 300
#define MI 0
#define GPF3CON 0x114001e0
#define GPF3DAT 0x114001e4
static dev_t no; 
static unsigned count = 1;
static const char * name  = "mydev";
static struct cdev mycdev;
static int* gpf3con = NULL;
static int* gpf3dat = NULL;

static long myioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    switch(cmd){
        case(LED5_ON):
            writel(readl(gpf3dat)|(0x1<<5),gpf3dat);
            break;
        case(LED5_OFF):
            writel(readl(gpf3dat)&~(0x1<<5),gpf3dat);
            break;
        default:
            printk("switch error");
    }
    return 0;
}
static const struct file_operations myfop = {
    .unlocked_ioctl = myioctl,
};
static void addr_map(void)
{
    gpf3con = ioremap(GPF3CON,4);
    gpf3dat = ioremap(GPF3DAT,4);
}
static void dev_init(void)
{
    writel((readl(gpf3con)&~(0xf<<20))|(0x1<<20),gpf3con);
    writel(readl(gpf3dat)&~(0x1<<5),gpf3dat);
}
static void addr_unmap(void)
{
    iounmap(gpf3con);
    iounmap(gpf3dat);
}

static int myinit(void)
{
    //注册
    int ret = -1;
    no = MKDEV(MA,MI);
    ret = register_chrdev_region(no,count,name);
    if(ret != 0)
        return ret;
    //初始化
    cdev_init(&mycdev,&myfop);
    //添加到内核
    ret = cdev_add(&mycdev,no,count);
    if(ret != 0)
    {
        unregister_chrdev_region(no,count);
        return ret;
    }
    //硬件部分
    addr_map();

    dev_init();

    return 0;
}

static void myexit(void)
{
    cdev_del(&mycdev);
    unregister_chrdev_region(no,count);
    //硬件部分
    addr_unmap();
}

module_init(myinit);
module_exit(myexit);
MODULE_LICENSE("GPL");

多了虚拟内存的映射和硬件设备初始化,在退出时解除初始化;利用file_operation的ioctl实现功能。

03_平台驱动和自动生成结点

#include<linux/init.h>
#include<linux/module.h>
#include<asm-generic/ioctl.h>
#include<linux/fs.h>
#include<linux/platform_device.h>
#include<asm/io.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/of.h>

#include"cmd.h"
#define MA 300
#define MI 0
struct class *my_class = NULL;
struct device *my_dev;
static dev_t no;
static unsigned count=1;
static char* name ="mydev";
static struct cdev mydev;
static char *gpf3con = NULL;
static int *gpf3dat = NULL;
static long myioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    switch(cmd){
        case(LED5_ON):
            writel(readl(gpf3dat)|(0x1<<5),gpf3dat);
            break;
        case(LED5_OFF):
            writel(readl(gpf3dat)&~(0x1<<5),gpf3dat);
            break;
        default:
            printk("switch error\n");
    }
    return 0;
}
static const struct file_operations fops = {
    .unlocked_ioctl = myioctl,
};
int ret = -1;
void addr_map(struct platform_device *pdev)
{
    struct resource *rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
    struct resource *resdat = platform_get_resource(pdev,IORESOURCE_MEM,1);
    gpf3con = ioremap(rescon->start,4);
    gpf3dat = ioremap(resdat->start,4);
}
void addr_unmap(void)
{
    iounmap(gpf3con);
    iounmap(gpf3dat);
}


int mydev_probe(struct platform_device *pdev)
{
    no = MKDEV(MA,MI);
    ret = register_chrdev_region(no,count,name);
    if(ret!=0){
        printk("register error\n");
        return -1;
    }
    cdev_init(&mydev,&fops);
    ret = cdev_add(&mydev,no,count);
    if(ret!=0){
        printk("add error\n");
        unregister_chrdev_region(no,count);
        return -1;
    }
    addr_map(pdev);
    //利用class_create函数和device_create函数创建设备结点
    my_class = class_create(THIS_MODULE,"fs_class");
    if(IS_ERR(my_class)){
        printk("Err:failed in creating class\n");
        cdev_del(&mydev);
        unregister_chrdev_region(no,count);
        addr_unmap();
        return -1;
    }
    my_dev = device_create(my_class,NULL,no,NULL,name);
    if(IS_ERR(my_dev)){
        printk("Err:failed in creating device\n");
        cdev_del(&mydev);
        unregister_chrdev_region(no,count);
        addr_unmap();
        class_destroy(my_class);
        return -1;
    }
    return 0;
}
int mydev_drv_remove(struct platform_device *pdev)
{
    //删除自动生成的结点
    device_destroy(my_class,no);
    class_destroy(my_class);
    cdev_del(&mydev);
    unregister_chrdev_region(no,count);
    addr_unmap();
    return 0;
}

//平台驱动的注册,移植方式参考其他文件
//在dm9000.c找到移植过来,ctrl+f替换dm9000为mydev
#ifdef CONFIG_OF
static const struct of_device_id mydev_of_matches[] = {
    { .compatible = "fs,myled", },                 ///和设备树匹配
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mydev_of_matches);
#endif

static struct platform_driver mydev_driver = {
    .driver    = {
        .name    = "mydev",
        .owner     = THIS_MODULE,
        //电源不要 .pm     = &mydev_drv_pm_ops,
        .of_match_table = of_match_ptr(mydev_of_matches),
    },
    .probe   = mydev_probe,
    .remove  = mydev_drv_remove,
};

module_platform_driver(mydev_driver);

MODULE_LICENSE("GPL");

自动创建结点时,创建类失败则要删除设备、解除注册、取消映射,创建设备失败多一步删除类。

平台总线(包含平台设备和平台驱动)注册是移植过来的。

03_01平台驱动概念

正常设备系统的组成有四个关键:设备、驱动、总线、类

在总线上挂载设备和驱动,运行设备时,通过总线的方式找到对应驱动。其中相似的设备可以通过定义为一类来优化重复代码框架。有的设备没有总线,则用软件模拟总线,这就是平台总线。

平台设备:修改设备树文件(dts文件)添加平台设备,在驱动中和of_device_id里的.compatible的设备名字匹配

例如(芯片是armv7架构coretex a9系类的exyno4412)LED5:

   myled@114001e0{
        compatible = "fs,myled"; //编,和驱动匹配
        //reg = <0x114001e0 0x4>,<0x114001e4 0x4>; //地址,大小    
        reg = <0x114001e0 0x8>; 
    }

key2外部中断:

    compatible = "fs,mykey";
        interrupt-parent = <&gpx1>;
        interrupts = <1 2>;
    };

平台驱动:跟file_operations相似,用.probe和.remove替换两个入口函数。

04_字符设备驱动练习

1. rtc 驱动 2. main 里面间隔 1s 刷新显示时钟
1. rtc 驱动 2. mian 里面写个 socket 服务器,通过 socket 每个 1s 传输一次时钟
3. ubuntu 写一个客户端,接收对方传输过来的数据,并实时显示。
 使用按键k3,按下开灯,按下关灯。(按键和led必须是2个独立的ko;一个main,2个独
立设备关联起来;)
蜂鸣器响和不响; 两只老虎(改变周期频率 100M/1/2/CNT = 527
第一个是硬件框架,第二个是结合网络编程,第三个是两个模块结合,第四个是利用音频(不同音阶对应不同频率)。
第④个:
应用程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/ioctl.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define LED_ON _IO('L', 0x01)
#define LED_OFF _IO('L', 0x02)

#define BUZZER_ON _IO('B', 1)
#define BUZZER_OFF _IO('B', 2)
#define BUZZER_SET _IOW('B', 3, int)

#define Do 262
#define Re 294
#define Mi 330
#define Fa 349
#define So 392
#define La 440
#define Xi 494

int main(void)
{

    //int data[7] = {Do, Re, Mi, Fa, So, La, Xi};
    int data[] = {
        Mi, So, So, Xi, Mi, Do, Do, Re, Mi, Mi, Re, Do,
        Mi, So, So, Xi, Mi, Do, Do, Re, Mi, Mi, Re, Do,
        Mi, So, So, Xi, Mi, Do, Do, Re, Mi, Mi, Re, Do,
        Mi, So, So, Xi, Mi, Do, Do, Re, Mi, Mi, Re, Do,
        Fa, Fa, Fa, Xi, So, So, Re,
        Mi, So, So, Xi, Mi, Do, Do, Re, Mi, Mi, Re, Re, Do
    };

    int led_fd;
    int key_fd;
    int buzzer_fd;
    int ret;


    buzzer_fd = open("/dev/buzzer", O_RDWR);
    if (buzzer_fd < 0)
    {
        perror("open buzzer");
        return -1;
    }
    int i;

    ioctl(buzzer_fd, BUZZER_ON);
    //while (1)
    //{

        for (i = 0; i < sizeof(data)/sizeof(data[0]); i++)
        {
            ioctl(buzzer_fd, BUZZER_SET, data[i]);
            usleep(1000*100);
        }
        ioctl(buzzer_fd, BUZZER_OFF);

        // ioctl(buzzer_fd, BUZZER_ON);
        // sleep(1);
        // ioctl(buzzer_fd, BUZZER_OFF);
        // sleep(1);
    //}

    close(buzzer_fd);

    return 0;
}

驱动:
// 内核模块
// 基于内核模块 实现平台驱动框架
// 基于平台驱动框架 添加字符设备驱动框架
// 基于字符设备驱动框架 实现各种驱动
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/kdev_t.h>
#include <asm/uaccess.h>

#define BUZZER_ON _IO('B', 1)
#define BUZZER_OFF _IO('B', 2)
#define BUZZER_SET _IOW('B', 3,int)

dev_t dev_no;
struct cdev key_cdev;
char *device_name = "buzzer";
char *class_name = "buzzer_class";
struct class *buzzer_class;
struct device *buzzer_drv;
unsigned int *vir_con;

unsigned int *vir_tcfg0;
unsigned int *vir_tcfg1;
unsigned int *vir_tcon;
unsigned int *vir_tcntb0;
unsigned int *vir_tcmpb0;

#define LA   440

long  buzzer_ioctl (struct file *, unsigned int, unsigned long);

const struct file_operations led_fops = {
	.unlocked_ioctl = buzzer_ioctl,
};

// 无源蜂鸣器 相关操作
void buzzer_add_umap(void);
void buzzer_config(void);
void buzzer_start(void);
void buzzer_stop(void);

int dev_probe(struct platform_device *dev);
int dev_remove(struct platform_device *dev);

static const struct of_device_id led_of_match[] = {
	{.compatible = "fs,buzzer"},
	{}};
struct platform_driver buzzer_driver =
	{
		.driver = {
			.name = "fs,buzzer",
			.owner = THIS_MODULE,
			.of_match_table = of_match_ptr(led_of_match),
		},
		.probe = dev_probe,
		.remove = dev_remove,
};

static int mod_init(void)
{
	return platform_driver_register(&buzzer_driver);
}

static void mod_exit(void)
{
	return platform_driver_unregister(&buzzer_driver);
}

module_init(mod_init);
module_exit(mod_exit);

MODULE_LICENSE("GPL");

int dev_probe(struct platform_device *dev)
{
    printk("11111\n");
	struct resource *res_con;
	struct resource *res_pwm;
	// 创建字符设备
	int ret;
	// 申请设备号
	ret = alloc_chrdev_region(&dev_no, 0, 1, device_name);
	if (ret < 0)
	{
		goto alloc_failed;
	}

	// cdev_init
	cdev_init(&key_cdev, &led_fops);

	// cdev_add
	ret = cdev_add(&key_cdev, dev_no, 1);
	if (ret < 0)
	{
		goto cdev_add_failed;
	}
	// class_create
	buzzer_class = class_create(THIS_MODULE, class_name);
	if (IS_ERR(buzzer_class))
	{
		goto class_create_failed;
	}

	// device_create
	buzzer_drv = device_create(buzzer_class, NULL, dev_no, NULL, device_name);
	if (IS_ERR(buzzer_drv))
	{
		goto device_create_failed;
	}

	// 从设备树获取硬件资源(寄存器地址)
	res_con = platform_get_resource(dev, IORESOURCE_MEM, 0);
	res_pwm = platform_get_resource(dev, IORESOURCE_MEM, 1);
	if (res_con == NULL || res_pwm == NULL)
	{
		goto platform_get_resource;
	}

	printk("config:%#x data:%#x\n", res_con->start, res_pwm->start);

	vir_con = (unsigned int *)ioremap(res_con->start, 4);
	if (vir_con == NULL)
	{
		goto ioremap_con_failed;
	}

	vir_tcfg0 = (unsigned int *)ioremap(res_pwm->start, 4);
	if(vir_tcfg0 == NULL){
		goto ioremap_tcfg0_failed;
	}
	vir_tcfg1 = (unsigned int *)ioremap(res_pwm->start+0x4, 4);
	if(vir_tcfg1 == NULL){
		goto ioremap_tcfg1_failed;
	}

	vir_tcon = (unsigned int *)ioremap(res_pwm->start+0x8, 4);
	if(vir_tcon == NULL){
	    
		goto ioremap_tcon_failed;
	}
	vir_tcntb0 = (unsigned int *)ioremap(res_pwm->start+0xc, 4);
	if(vir_tcntb0 == NULL){
		goto ioremap_tcntb0_failed;
	}

	vir_tcmpb0 = (unsigned int *)ioremap(res_pwm->start+0x10, 4);
	if(vir_tcmpb0 == NULL){
		goto ioremap_tcmpb0_failed;
	}

	buzzer_config();

	// buzzer_start();


	return 0;

ioremap_tcmpb0_failed:
	iounmap(vir_tcntb0);
ioremap_tcntb0_failed:
	iounmap(vir_tcon);
ioremap_tcon_failed:
	iounmap(vir_tcfg1);
ioremap_tcfg1_failed:
	iounmap(vir_tcfg0);
ioremap_tcfg0_failed:
	iounmap(vir_con);
ioremap_con_failed:
platform_get_resource:
	device_destroy(buzzer_class, dev_no);
device_create_failed:
	class_destroy(buzzer_class);
class_create_failed:
	cdev_del(&key_cdev);
cdev_add_failed:
	unregister_chrdev_region(dev_no, 1);
alloc_failed:
	return -1;
}

int dev_remove(struct platform_device *dev)
{
	// 销毁字符设备
	buzzer_add_umap();

	// device_destroy
	device_destroy(buzzer_class, dev_no);

	// class_destroy
	class_destroy(buzzer_class);

	// cdev_del
	cdev_del(&key_cdev);

	// 释放设备号
	unregister_chrdev_region(dev_no, 1);

	return 0;
}


void buzzer_add_umap(void)
{
	iounmap(vir_con);
	iounmap(vir_tcfg0);
	iounmap(vir_tcfg1);
	iounmap(vir_tcon);
	iounmap(vir_tcntb0);
	iounmap(vir_tcmpb0);
}
void buzzer_config(void)
{
	*vir_con = (*vir_con) & ~(0xf);
	*vir_con = (*vir_con) | (0x2);   //配置为pmw功能

	*vir_tcfg0 |= 0xff;     //预分配256
	*vir_tcfg1= *vir_tcfg1 &(~0xf);
	*vir_tcfg1 = *vir_tcfg1 | (0x3);   // 8分频

	//F_tin = 100Mhz / 256 /8 = 48828hz

	*vir_tcntb0 = 48828/LA;
	*vir_tcmpb0 = (48828/LA)/2;

	*vir_tcon = *vir_tcon|(0x1<<3); 
	*vir_tcon = (*vir_tcon) | (0x1<<1);  
	*vir_tcon = (*vir_tcon) & ~(0x1<<1); 
}
void buzzer_set_freq(int freq)
{
	*vir_tcntb0 = 48828/freq;
	*vir_tcmpb0 = (48828/freq)/2;
}
void buzzer_start(void)
{
	*vir_tcon = (*vir_tcon) | (0x1);
}
void buzzer_stop(void)
{
	*vir_tcon = (*vir_tcon) & ~(0x1);
}

long  buzzer_ioctl (struct file *pf, unsigned int cmd, unsigned long arg)
{
	switch (cmd)
	{
	case BUZZER_ON:
		printk("buzzer on\n");
		buzzer_start();
		break;
	case BUZZER_OFF:
		printk("buzzer off\n");
		buzzer_stop();
		break;
	case BUZZER_SET:
		buzzer_set_freq(arg);
		break;
	default:
		break;
	}

	return 0;
}

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux字符设备驱动框架可以分为三个层次:用户空间、内核空间和硬件。 1. 用户空间 在用户空间,应用程序通过标准的文件操作(open, read, write, close, ioctl 等)来访问设备。这些操作被封装成系统调用,应用程序通过系统调用来与设备驱动进行交互。 2. 内核空间 在内核空间,设备驱动程序负责处理用户空间发来的请求,并将它们转换为硬件操作。在 Linux 中,设备驱动程序通常以模块的形式存在,可以通过 insmod 和 rmmod 命令来加载和卸载。 设备驱动程序主要包括以下几个部分: (1)初始化和清理函数:在 Linux 加载驱动模块时,会自动调用驱动程序中的初始化函数来完成设备的初始化工作。而在卸载模块时,会自动调用清理函数来完成设备的清理工作。 (2)设备注册和注销函数:设备驱动程序需要通过设备注册函数将设备注册到系统中,并通过设备注销函数将设备从系统中注销。 (3)文件操作函数:文件操作函数包括 open、read、write、ioctl、release 等。这些函数是设备驱动程序的核心部分,负责处理用户空间发来的请求,并将它们转换为硬件操作。 (4)中断处理函数:在 Linux 中,驱动程序可以注册中断处理函数来处理硬件中断。当硬件发生中断时,内核会自动调用相应的中断处理函数。 3. 硬件 在硬件方面,设备驱动程序需要与硬件进行交互,包括对硬件进行初始化、读写寄存器等操作。这些操作通常通过 I/O 端口、内存映射等方式进行。在 Linux 中,访问硬件通常需要使用 I/O 端口映射或者内存映射技术。 ### 回答2: Linux下的字符设备驱动框架是由设备驱动程序和字符设备接口组成的。字符设备是一种面向流的数据传输,以字符为单位进行读写的设备,如串行端口、终端设备等。 在Linux中,字符设备驱动程序主要包括三个组件:设备文件操作函数、设备注册与注销函数,以及字符设备驱动结构体。 设备文件操作函数是驱动程序中的核心部分,它包括对设备的打开、关闭、读取和写入等操作。这些函数在应用程序通过系统调用访问设备文件时被调用,并通过设备文件描述符和设备驱动结构体之间进行传递。 设备注册与注销函数用于将驱动程序注册到系统中,并在不再使用时将其注销。这些函数通常在模块加载和卸载时调用,以便系统能够正确地识别和管理设备。 字符设备驱动结构体是驱动程序的核心数据结构,它包含了设备文件操作函数的指针以及其他驱动程序需要的信息。该结构体在驱动程序注册时被创建,并在使用过程中被驱动程序的其他组件所引用。 在Linux中,字符设备接口提供了一组函数,用于用户空间应用程序与字符设备驱动程序之间的通信。这些函数包括open()、close()、read()、write()等,通过这些函数,应用程序可以打开设备文件并对其进行读写操作。 总之,Linux下的字符设备驱动框架包括设备驱动程序和字符设备接口两个组成部分,其中设备驱动程序包括设备文件操作函数、设备注册与注销函数和字符设备驱动结构体,字符设备接口提供了一组函数,用于应用程序与驱动程序之间的通信。这样的框架使得开发者能够轻松地编写和管理字符设备驱动程序,在Linux系统中实现更多种类的字符设备。 ### 回答3: Linux字符设备驱动框架主要包括设备描述结构体结构(struct cdev)、设备号分配和注册(dev_t、alloc_chrdev_region、cdev_init、cdev_add)、文件操作函数(open、read、write、release等)、设备文件的创建和删除(mkdev、mknod)以及设备的注册和注销等几个关键步骤。 在字符设备驱动框架中,首先需要定义一个设备描述结构体struct cdev,该结构体包含了设备操作函数指针、主设备号、次设备号等信息。接着通过alloc_chrdev_region函数进行设备号的分配和注册,或者使用register_chrdev_region函数进行设备号的注册。然后使用cdev_init函数对设备描述结构体进行初始化,并使用cdev_add函数将设备添加到系统中。 在文件操作函数上,驱动需要实现open、read、write、release等函数,用于处理用户空间对设备文件的打开、读取、写入和关闭操作。这些函数对应着相应的系统调用,通过在驱动中实现这些函数,可以与用户空间进行交互。 在设备文件的创建和删除上,可以使用mknod命令手动创建设备文件,也可以通过mkdev函数在驱动代码中自动创建设备文件。 最后,在设备注册和注销的过程中,通过register_chrdev和unregister_chrdev函数将字符设备注册和注销到系统中,系统会根据设备号调用相应的驱动进行操作,完成设备的初始化和卸载过程。 总之,Linux字符设备驱动框架主要包括设备描述结构体结构、设备号的分配和注册、文件操作函数的实现、设备文件的创建和删除,以及设备的注册和注销等几个关键步骤,通过这些步骤可以实现字符设备驱动和用户空间的交互。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值