上一篇文章學習了字元裝置的註冊,操作過的小夥伴都知道上一篇文章中測試驅動時是通過手動建立裝置節點的,現在開始學習怎麼自動掛載裝置節點和裝置樹資訊的獲取,這篇文章中的原始碼將會是我以後編寫字元驅動的模板。
一、準備材料
開發環境:VMware
作業系統:ubuntu
開發版:湃兔i2S-6UB
庫檔案:linux開發板或ubuntu的核心原始碼
二、自動建立裝置節點
需要用到的標頭檔案 #include <linux/device.h>,需要用到的函式如下所示
struct class *class_create(owner, name)
void class_destroy(struct class *class)
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
void device_destroy(struct class *class, dev_t devt);
owner: THIS_MODULE
name: 裝置節點的名稱(也就是/dev目錄下的檔名)
class:類
parent:NULL
devt:裝置號
drvdata:NULL
fmt:裝置節點的名稱
三、獲取裝置樹資訊
為了幫助像我一樣才接觸linux驅動,對裝置樹不是很理解的小夥伴,所繫這裡就不對裝置樹進行詳細的介紹。可以將裝置樹簡單的理解為,裝置樹的存在是方便linux核心研究人員專心的研究核心的功能,通過裝置樹將板載的描述檔案和核心分開,使得核心檔案不在臃腫。有需要的小夥伴可以瞭解Device Tree。
裝置樹檔案在核心原始碼的“arch/arm/boot/dts”目錄下,裝置樹的描述檔案是'.dtsi',每個開發板對應的檔案不同,比如我的開發板的描述檔案是i2c6ulxb-i2s6ull-emmc.dtsi,開啟可以看到的資訊如圖所示:
在這裡我就不對裝置進行更改了,我對backlight節點資訊進行讀取,有需要了解裝置樹語法的小夥伴可以瞭解Linux裝置樹語法詳解。
我在驅動中讀取裝置樹的主要函式有以下幾個,想了解更多of函式的小夥伴可以瞭解linux裝置樹常用of操作函式。
inline struct device_node *of_find_node_by_path(const char *path)
int of_property_read_string(struct device_node *nd, const char *propname, const char *out_string);
int of_property_read_u32_array(struct device_node *nd, const char *propname, u32 *out_value)
path:帶有全路徑的節點名,可以使用節點的別名
np:裝置節點
proname 要讀取的屬性名字
out_string:讀取到的字串值
out_value:讀取到的陣列值
通過這幾個函式,就可以將裝置樹種的資訊的讀取出來了,接下載看原始碼
四、程式原始碼
驅動檔案chrdevtemp.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define CHRDEVTEMP_NAME "chrdevtemp"
#define CHRDEVTEMP_COUNT 1
/* 裝置結構體 */
struct chrtemp_dev{
dev_t devid; /* 裝置號 */
int major; /* 主裝置號 */
int minor; /* 次裝置號 */
struct cdev cdev; /* 字元裝置 */
struct class *class; /* 類結構體 */
struct device *device; /* 裝置 */
struct device_node *nd; /* 裝置節點 */
int gpio_number; /*gpio的編號*/
};
struct chrtemp_dev chrdevtemp;
static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"hello This is the kernel data"};
static int chrdevtemp_open(struct inode *inode, struct file *filp)
{
filp->private_data = &chrdevtemp;
return 0;
}
static int chrdevtemp_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t chrdevtemp_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos)
{
int ret = 0;
memcpy(readbuf, kerneldata, sizeof(kerneldata));
ret = copy_to_user(buf, readbuf, count);
if (ret == 0) {
} else {
}
return 0;
}
static ssize_t chrdevtemp_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
ret = copy_from_user(writebuf, buf, count);
if (ret == 0) {
printk("kernel recevdata:%s\r\n", writebuf);
} else {
}
return 0;
}
/*
* 字元裝置操作集合
*/
static const struct file_operations chrdevtemp_fops = {
.owner = THIS_MODULE,
.open = chrdevtemp_open,
.release = chrdevtemp_release,
.read = chrdevtemp_read,
.write = chrdevtemp_write,
};
/*
* 模組入口
*/
static int __init chrdevtemp_init(void)
{
int ret = 0;
const char *str;
u32 brightness[8];
u8 i;
printk("chrdevtemp_init\r\n");
/* 申請裝置號 */
chrdevtemp.major = 0; /* 設定裝置號由記憶體分配 */
if (chrdevtemp.major){
chrdevtemp.devid = MKDEV(chrdevtemp.major, 0);
ret = register_chrdev_region(chrdevtemp.devid, CHRDEVTEMP_COUNT, CHRDEVTEMP_NAME);
} else {
ret = alloc_chrdev_region(&chrdevtemp.devid, 0, CHRDEVTEMP_COUNT, CHRDEVTEMP_NAME);
chrdevtemp.major = MAJOR(chrdevtemp.devid);
chrdevtemp.minor = MINOR(chrdevtemp.devid);
}
if (ret < 0) {
printk("chrdevtemp chrdev_region err!\r\n");
goto fail_devid;
}
/* 註冊字元裝置 */
chrdevtemp.cdev.owner = chrdevtemp_fops.owner;
cdev_init(&chrdevtemp.cdev, &chrdevtemp_fops);
ret = cdev_add(&chrdevtemp.cdev, chrdevtemp.devid, CHRDEVTEMP_COUNT);
if (ret < 0) {
goto fail_cdev;
}
/* 自動建立裝置節點 */
chrdevtemp.class = class_create(THIS_MODULE, CHRDEVTEMP_NAME);
if (IS_ERR(chrdevtemp.class)) {
ret = PTR_ERR(chrdevtemp.class);
goto fail_class;
}
chrdevtemp.device = device_create(chrdevtemp.class, NULL, chrdevtemp.devid, NULL, CHRDEVTEMP_NAME);
if (IS_ERR(chrdevtemp.device)) {
ret = PTR_ERR(chrdevtemp.device);
goto fail_device;
}
/* 獲取裝置樹的屬性內容 */
chrdevtemp.nd = of_find_node_by_path("/backlight");
if (chrdevtemp.nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
}
/* 獲取字串屬性 */
ret = of_property_read_string(chrdevtemp.nd, "compatible", &str);
if (ret < 0) {
goto fail_rs;
} else {
printk("status is: %s\r\n", str);
}
/* 獲取陣列 */
ret = of_property_read_u32_array(chrdevtemp.nd, "brightness-levels", brightness, 8);
if (ret < 0) {
goto fail_rs;
} else {
printk("brightness-levels: ");
for(i = 0; i < 8; i++){
printk("%d ", brightness[i]);
}
printk("\r\n");
}
return 0;
fail_rs:
fail_findnd:
device_destroy(chrdevtemp.class, chrdevtemp.devid);
fail_device:
class_destroy(chrdevtemp.class);
fail_class:
cdev_del(&chrdevtemp.cdev);
fail_cdev:
unregister_chrdev_region(chrdevtemp.devid, CHRDEVTEMP_COUNT);
fail_devid:
return ret;
}
/*
* 模組出口
*/
static void __exit chrdevtemp_exit(void)
{
printk("chrdevtemp_exit\r\n");
/* 刪除字元裝置 */
cdev_del(&chrdevtemp.cdev);
/* 釋放字元設號 */
unregister_chrdev_region(chrdevtemp.devid, CHRDEVTEMP_COUNT);
/* 摧毀裝置 */
device_destroy(chrdevtemp.class, chrdevtemp.devid);
/* 摧毀類 */
class_destroy(chrdevtemp.class);
}
/*
* 模組註冊入口
*/
module_init(chrdevtemp_init);
/*
* 模組註冊出口
*/
module_exit(chrdevtemp_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiaozhu");
Makefile檔案
KERNELDIR := /home/xfg/linux/imx_7ull/i2x_6ub/system_file/i2SOM-iMX-Linux
CURRENT_PATH := $(shell pwd)
obj-m := chrdevtemp.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
測試程式需就使用之前編寫的hello2App.c檔案
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
*argc:應用程式引數個數
*argv[]:具體的引數內容,字串形式
*./hello2App <filename> <1:2> 1表示讀,2表示寫
*./hello2App /dev/hello2 1 表示從驅動裡面讀資料
*./hello2App /dev/hello2 2 表示向驅動裡面寫資料
* */
int main(int argc, char *argv[])
{
int ret = 0;
int fd = 0;
char *filename;
char readbuf[100], writebuf[100];
static char usrdata[] = {"hell0 This is user data!"};
if(argc !=3) {
printf("Instruction usage error!!!\r\n");
printf("./helle2App <filename> <1:2> 1表示讀,2表示寫\r\n");
printf("./hello2App ./dev/hello2 1 \r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
}
if(atoi(argv[2]) ==1){
ret = read(fd, readbuf, 50);
if(ret <0) {
printf("read file %s failed!\r\n", filename);
} else {
printf("App read data:%s\r\n", readbuf);
}
}
if(atoi(argv[2]) == 2) {
memcpy(writebuf, usrdata, sizeof(usrdata));
ret = write(fd,writebuf, 50);
if(ret <0) {
printf("write file %s failed\r\n", filename);
} else {
}
}
ret =close(fd);
if(ret <0) {
printf("close file %s falied!\r\n", filename);
}
return 0;
}
五、測試
將驅動檔案和應用檔案進行編譯
make
arm-linux-gnueabihf-gcc
將編譯後的驅動檔案可應用檔案拷貝到開發板中,然後載入驅動,結果如下圖所示:
將讀取的資訊和裝置樹檔案中的資訊對比,說明讀取成功。
通過應用讀取裝置節點,測試裝置節點是否載入成功,結果如下圖所示:
可知通過自動創界裝置節點成功。
六、問題
1.自動建立裝置節點時出現如下錯誤
解決辦法:此錯誤是因為我執行的核心版本和寫驅動是用的核心版本不一致導致,只需要控制版本一致後從新燒寫核心檔案即可。
參考文獻
Device Tree:http://www.wowotech.net/device_model/why-dt.html
Linux裝置樹語法詳解:https://www.cnblogs.com/xiaojiang1025/p/6131381.html
linux裝置樹常用of操作函式:https://blog.csdn.net/wang_518/article/details/108923399