新字元驅動框架驅動LED

lethe1203發表於2024-03-23
程式碼參考正點原子
https://www.cnblogs.com/lethe1203/p/18091283一文中,存在以下的幾個問題:
1、使用register_chrdev函式註冊字元裝置,浪費了大量的次裝置號,而且需要手動指定主裝置號,還需要事先確認好哪一個主裝置號沒用
2、需要手動mknode建立裝置節點

解決問題1:

使用裝置號的時候向Linux核心申請,需要幾個就申請幾個,由Linux核心分配裝置可以使用的裝置號,如果沒有指定裝置號的話就可以使用以下函式來申請裝置號:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果給定了裝置的主裝置號和次裝置號就使用下面函式來註冊裝置號即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)

// 引數說明
引數 from 是要申請的起始裝置號,也就是給定的裝置號;引數 count 是要申請的數量,一
般都是一個;引數 name 是裝置名字。
注 銷 字 符 設 備 之 後 要 釋 放 掉 設 備 號 , 不 管 是 通 過 alloc_chrdev_region 函 數 還 是register_chrdev_region 函式申請的裝置號,統一使用如下釋放函式:
void unregister_chrdev_region(dev_t from, unsigned count)
這樣,就可以生成一個同一個框架:
int major; /* 主裝置號 */
int minor; /* 次裝置號 */
dev_t devid; /* 裝置號 */

if (major) { /* 定義了主裝置號 */
    devid = MKDEV(major, 0); /* 大部分驅動次裝置號都選擇 0 */
    register_chrdev_region(devid, 1, "test");
} else { /* 沒有定義裝置號 */
    alloc_chrdev_region(&devid, 0, 1, "test"); /* 申請裝置號 */
    major = MAJOR(devid); /* 獲取分配號的主裝置號 */
    minor = MINOR(devid); /* 獲取分配號的次裝置號 */
}

解決問題2:

cdev表示一個字元裝置,cdev結構體在include/linux/cdev.h檔案中定義:
 struct cdev {
         struct kobject kobj;
         struct module *owner;
         const struct file_operations *ops;
         struct list_head list;
         dev_t dev;
         unsigned int count;
 };
在 cdev 中有兩個重要的成員變數:ops 和 dev,這兩個就是字元裝置檔案操作函式集合file_operations 以及裝置號 dev_t。
cdev相關的函式:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)    // 初始化cdev變數
int cdev_add(struct cdev *p, dev_t dev, unsigned count)                // 向 Linux 系統新增字元裝置(cdev 結構體變數)
void cdev_del(struct cdev *p)                                           // 解除安裝驅動的時候一定要使用 cdev_del 函式從 Linux 核心中刪除相應的字元裝置

class類

建立class類:
#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)

// 引數說明
引數owner 一般為 THIS_MODULE
引數 name 是類名字
刪除類:
void class_destroy(struct class *cls);

建立裝置device:

struct device *device_create(struct class *class, 
                struct device *parent,
                dev_t devt, 
                void *drvdata, 
                const char *fmt, ...)

// 引數說明
引數 class 就是裝置要建立哪個類下面;
引數 parent 是父裝置,一般為 NULL,也就是沒有父裝置
引數 devt 是裝置號;引數 drvdata 是裝置可能會使用的一些資料,一般為 NULL;
引數 fmt 是裝置名字,如果設定 fmt=xxx 的話,就會生成/dev/xxx這個裝置檔案。
返回值就是建立好的裝置。

刪除裝置:

void device_destroy(struct class *class, dev_t devt)

// 引數說明
引數 class 是要刪除的裝置所處的類
引數 devt 是要刪除的裝置號。

建立class和device的demo如下:
struct class *class; /**/ 
struct device *device; /* 裝置 */
dev_t devid; /* 裝置號 */ 

/* 驅動入口函式 */
static int __init led_init(void)
{
    /* 建立類 */
    class = class_create(THIS_MODULE, "xxx");
    /* 建立裝置 */
    device = device_create(class, NULL, devid, NULL, "xxx");
    return 0;
}

/* 驅動出口函式 */
static void __exit led_exit(void)
{
    /* 刪除裝置 */
    device_destroy(newchrled.class, newchrled.devid);
    /* 刪除類 */
    class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);

LED在新字元驅動框架下的驅動newled.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define NEWCHRLED_CNT            1              /* 裝置號個數 */
#define NEWCHRLED_NAME            "newchrled"    /* 名字 */
#define LEDOFF                     0            /* 關燈 */
#define LEDON                     1            /* 開燈 */
 
/* 暫存器實體地址 */
#define CCM_CCGR1_BASE                (0X020C406C)    
#define SW_MUX_GPIO1_IO03_BASE        (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE        (0X020E02F4)
#define GPIO1_DR_BASE                (0X0209C000)
#define GPIO1_GDIR_BASE                (0X0209C004)

/* 對映後的暫存器虛擬地址指標 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* newchrled裝置結構體 */
struct newchrled_dev{
    dev_t devid;            /* 裝置號      */
    struct cdev cdev;        /* cdev     */
    struct class *class;        /**/
    struct device *device;    /* 裝置      */
    int major;                /* 主裝置號      */
    int minor;                /* 次裝置號   */
};

struct newchrled_dev newchrled;    /* led裝置 */

/*
 * @description        : LED開啟/關閉
 * @param - sta     : LEDON(0) 開啟LED,LEDOFF(1) 關閉LED
 * @return             : 無
 */
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);    
        writel(val, GPIO1_DR);
    }else if(sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val|= (1 << 3);    
        writel(val, GPIO1_DR);
    }    
}

/*
 * @description        : 開啟裝置
 * @param - inode     : 傳遞給驅動的inode
 * @param - filp     : 裝置檔案,file結構體有個叫做private_data的成員變數
 *                       一般在open的時候將private_data指向裝置結構體。
 * @return             : 0 成功;其他 失敗
 */
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchrled; /* 設定私有資料 */
    return 0;
}

/*
 * @description        : 從裝置讀取資料 
 * @param - filp     : 要開啟的裝置檔案(檔案描述符)
 * @param - buf     : 返回給使用者空間的資料緩衝區
 * @param - cnt     : 要讀取的資料長度
 * @param - offt     : 相對於檔案首地址的偏移
 * @return             : 讀取的位元組數,如果為負值,表示讀取失敗
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description        : 向裝置寫資料 
 * @param - filp     : 裝置檔案,表示開啟的檔案描述符
 * @param - buf     : 要寫給裝置寫入的資料
 * @param - cnt     : 要寫入的資料長度
 * @param - offt     : 相對於檔案首地址的偏移
 * @return             : 寫入的位元組數,如果為負值,表示寫入失敗
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];        /* 獲取狀態值 */

    if(ledstat == LEDON) {    
        led_switch(LEDON);        /* 開啟LED燈 */
    } else if(ledstat == LEDOFF) {
        led_switch(LEDOFF);    /* 關閉LED燈 */
    }
    return 0;
}

/*
 * @description        : 關閉/釋放裝置
 * @param - filp     : 要關閉的裝置檔案(檔案描述符)
 * @return             : 0 成功;其他 失敗
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 裝置操作函式 */
static struct file_operations newchrled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release =     led_release,
};

/*
 * @description    : 驅動出口函式
 * @param         : 無
 * @return         : 無
 */
static int __init led_init(void)
{
    u32 val = 0;

    /* 初始化LED */
    /* 1、暫存器地址對映 */
      IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
      SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /* 2、使能GPIO1時鐘 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);    /* 清楚以前的設定 */
    val |= (3 << 26);    /* 設定新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /* 3、設定GPIO1_IO03的複用功能,將其複用為
     *    GPIO1_IO03,最後設定IO屬性。
     */
    writel(5, SW_MUX_GPIO1_IO03);
    
    /*暫存器SW_PAD_GPIO1_IO03設定IO屬性
     *bit 16:0 HYS關閉
     *bit [15:14]: 00 預設下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 關閉開路輸出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驅動能力
     *bit [0]: 0 低轉換率
     */
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /* 4、設定GPIO1_IO03為輸出功能 */
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3);    /* 清除以前的設定 */
    val |= (1 << 3);    /* 設定為輸出 */
    writel(val, GPIO1_GDIR);

    /* 5、預設關閉LED */
    val = readl(GPIO1_DR);
    val |= (1 << 3);    
    writel(val, GPIO1_DR);

    /* 註冊字元裝置驅動 */
    /* 1、建立裝置號 */
    if (newchrled.major) {        /*  定義了裝置號 */
        newchrled.devid = MKDEV(newchrled.major, 0);
        register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
    } else {                        /* 沒有定義裝置號 */
        alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);    /* 申請裝置號 */
        newchrled.major = MAJOR(newchrled.devid);    /* 獲取分配號的主裝置號 */
        newchrled.minor = MINOR(newchrled.devid);    /* 獲取分配號的次裝置號 */
    }
    printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);    
    
    /* 2、初始化cdev */
    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &newchrled_fops);
    
    /* 3、新增一個cdev */
    cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

    /* 4、建立類 */
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.class)) {
        return PTR_ERR(newchrled.class);
    }

    /* 5、建立裝置 */
    newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.device)) {
        return PTR_ERR(newchrled.device);
    }
    
    return 0;
}

/*
 * @description    : 驅動出口函式
 * @param         : 無
 * @return         : 無
 */
static void __exit led_exit(void)
{
    /* 取消對映 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /* 登出字元裝置驅動 */
    cdev_del(&newchrled.cdev);/*  刪除cdev */
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 登出裝置號 */

    device_destroy(newchrled.class, newchrled.devid);
    class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lethe1203");

編譯Makefile:
 KERNELDIR := /mnt/d/project/imx6ull/atom/atom/linux

 CURRENT_PATH := $(shell pwd)

 obj-m :=newled.o

 all:
         $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

 clean:
         $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

ledApp測試APPhttps://www.cnblogs.com/lethe1203/p/18091363一文中的一樣,這裡不再說明
測試操作如下:
0

相關文章