大家好,我是逸珺。首先說聲抱歉,最近迷上釣魚了,有時候晚上出去夜釣大板鯽了,停更了一段時間。技術(shù)還是不太到家,遇到幾次大鯉魚都給溜了,心有不甘,所以最近花了比較多的時間。
言歸正傳,今天來分享一下以前寫一個中斷輸入設(shè)備驅(qū)動案例,希望對有需要的朋友能有所幫助。
背景介紹
在一個項(xiàng)目中,有這樣一個需求:
主控芯片采用ZYNQ,需要采集外部一個脈沖編碼輸入信號,這個信號是一個脈沖波形,脈沖數(shù)量代表測量結(jié)果。比如這有可能是一個電機(jī)的霍爾信號輸出,代表電機(jī)的轉(zhuǎn)速,也有可能是一個光柵編碼器的脈沖輸出,是什么并不重要。
這個電路本身,利用光耦實(shí)現(xiàn)了輸入測設(shè)備信號與采集端的電氣隔離。由于PS端該Bank的電平為3.3V,所以光耦的另一側(cè)也是3.3V。
ZYNQ的PS端運(yùn)行Linux程序,所以在這個場景下,要從應(yīng)用程序的角度將外部輸入信號用起來,就需要實(shí)現(xiàn)這樣一個設(shè)備驅(qū)動程序:
創(chuàng)建設(shè)備
在ZYNQ下,使用petalinux工具鏈,當(dāng)然本文中對于寫這個驅(qū)動程序本身換成其他的處理器從代碼的角度是類似的。
1.先運(yùn)行一下工具鏈環(huán)境變量腳本:
source /opt/pkg/petalinux/settings.sh
當(dāng)然也可以不用手動這樣運(yùn)行,設(shè)置成linux開發(fā)主機(jī)開機(jī)自動運(yùn)行,這里就不贅述怎么設(shè)置了,網(wǎng)上很多介紹。
2.創(chuàng)建設(shè)備
petalinux-create -t modules --name di-drv
這樣在現(xiàn)有的工程下,就自動創(chuàng)建設(shè)備文件:
./project-spec/meta-user/recipes-modules/di-drv/files/di-drv.c
修改設(shè)備樹
./project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
中添加
/include/ "system-conf.dtsi"
/ {
amba {
pinctrl_di_default: di-default {
mux {
groups = "gpio0_0_grp";
function = "gpio0";
};
conf {
pins = "MIO0";
io-standard = <1>;
bias-high-impedance;
slew-rate = <0>;
};
};
};
di {
compatible = "di-drv";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_di_default>;
di-gpios = <&gpio0 0 0>;
};
};
本文中,假定使用的IO引腳為PS_MIO0。
驅(qū)動代碼
修改上面生成的代碼di-drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>
#include <asm/io.h>
/* 設(shè)備節(jié)點(diǎn)名稱 */
#define DEVICE_NAME "di-drv"
/* 設(shè)備號個數(shù) */
#define DEVID_COUNT 1
/* 驅(qū)動個數(shù) */
#define DRIVE_COUNT 1
/* 主設(shè)備號 */
#define MAJOR_U
/* 次設(shè)備號 */
#define MINOR_U 0
struct di_dev {
/* 字符設(shè)備框架 */
dev_t devid; //設(shè)備號
struct cdev cdev; //字符設(shè)備
struct class *class; //類
struct device *device; //設(shè)備
struct device_node *nd; //設(shè)備樹的設(shè)備節(jié)點(diǎn)
spinlock_t lock; //自旋鎖變量
int di_gpio; //DI gpio號
__u32 di_pulses;//DI counter
unsigned int di_irq; //DI 中斷號
};
static struct di_dev di_char = {
.cdev = {
.owner = THIS_MODULE,
},
};
/* 中斷服務(wù)函數(shù) */
static irqreturn_t di_handler(int irq, void *dev)
{
di_char.di_pulses++;
return IRQ_RETVAL(IRQ_HANDLED);
}
/* open函數(shù)實(shí)現(xiàn), 對應(yīng)到Linux系統(tǒng)調(diào)用函數(shù)的open函數(shù) */
static int di_drv_open(struct inode *inode_p, struct file *file_p)
{
printk("di_drv module openedn");
file_p->private_data = &di_char;
return 0;
}
/* read函數(shù)實(shí)現(xiàn), 對應(yīng)到Linux系統(tǒng)調(diào)用函數(shù)的read操作 */
static ssize_t di_drv_read(struct file *file_p, char __user *buf, size_t len, loff_t *loff_t_p)
{
unsigned long flags;
int ret;
union e_int_conv{
__u8 buf[8];
__u32 di_raw;
};
/* 獲取鎖 */
spin_lock_irqsave(&di_char.lock, flags);
union e_int_conv di;
di.di_raw.di = di_char.di_pulses;
ret = copy_to_user(buf, di.buf, 8);
/* 釋放鎖 */
spin_unlock_irqrestore(&di_char.lock, flags);
return ret ? ret : 4;
}
/* release函數(shù)實(shí)現(xiàn), 對應(yīng)到Linux系統(tǒng)調(diào)用函數(shù)的close函數(shù) */
static int di_drv_release(struct inode *inode_p, struct file *file_p)
{
printk("di_drv module releasen");
return 0;
}
/* file_operations結(jié)構(gòu)體聲明 */
static struct file_operations di_fops = {
.owner = THIS_MODULE,
.open = di_drv_open,
.read = di_drv_read,
.release = di_drv_release,
};
/* 模塊加載時會調(diào)用的函數(shù) */
static int __init di_drv_init(void)
{
u32 ret = 0;
/* 初始化自旋鎖 */
spin_lock_init(&di_char.lock);
/** gpio框架 **/
/* 獲取設(shè)備節(jié)點(diǎn) */
di_char.nd = of_find_node_by_path("/di");
if(di_char.nd == NULL)
{
printk("di node not foundrrn");
return -EINVAL;
}
/* 獲取節(jié)點(diǎn)中g(shù)pio標(biāo)號 */
di_char.di_gpio = of_get_named_gpio(di_char.nd, "di-gpios", 0);
if(di_char.di_gpio < 0)
{
printk("Failed to get di-gpios from device treern");
return -EINVAL;
}
printk("di-gpio num = %drn", di_char.di_gpio);
/* 申請gpio標(biāo)號對應(yīng)的引腳 */
ret = gpio_request(di_char.di_gpio, "di-drv");
if(ret != 0)
{
printk("Failed to request di_gpiorn");
return -EINVAL;
}
/* 把這個io設(shè)置為輸入 */
ret = gpio_direction_input(di_char.di_gpio);
if(ret < 0)
{
printk("Failed to set di_gpio as inputrn");
return -EINVAL;
}
/* 獲取中斷號 */
di_char.di_irq = gpio_to_irq(di_char.di_gpio);
printk("di_irq number is %d rn", di_char.di_irq);
/* 申請中斷 */
ret = request_irq(di_char.di_irq,
di_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"di-drv",
NULL);
if(ret < 0)
{
printk("di_irq %d request failedrn", di_char.di_irq);
return -EFAULT;
}
/* 注冊設(shè)備號 */
alloc_chrdev_region(&di_char.devid, MINOR_U, DEVID_COUNT, DEVICE_NAME);
/* 初始化字符設(shè)備結(jié)構(gòu)體 */
cdev_init(&di_char.cdev, &di_fops);
/* 注冊字符設(shè)備 */
cdev_add(&di_char.cdev, di_char.devid, DRIVE_COUNT);
/* 創(chuàng)建類 */
di_char.class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(di_char.class))
{
return PTR_ERR(di_char.class);
}
/* 創(chuàng)建設(shè)備節(jié)點(diǎn) */
di_char.device = device_create( di_char.class, NULL,
di_char.devid, NULL,
DEVICE_NAME );
if(IS_ERR(di_char.device))
{
return PTR_ERR(di_char.device);
}
di_char.di_pulses = 0;
return 0;
}
/* 卸載模塊 */
static void __exit di_drv_exit(void)
{
/* 釋放gpio */
gpio_free(di_char.di_gpio);
/* 釋放中斷 */
free_irq(di_char.di_irq, NULL);
/* 注銷字符設(shè)備 */
cdev_del(&di_char.cdev);
/* 注銷設(shè)備號 */
unregister_chrdev_region(di_char.devid, DEVID_COUNT);
/* 刪除設(shè)備節(jié)點(diǎn) */
device_destroy(di_char.class, di_char.devid);
/* 刪除類 */
class_destroy(di_char.class);
printk("DI dev exit okn");
}
/* 標(biāo)記加載、卸載函數(shù) */
module_init(di_drv_init);
module_exit(di_drv_exit);
/* 驅(qū)動描述信息 */
MODULE_AUTHOR("Embinn");
MODULE_ALIAS("DI input");
MODULE_DESCRIPTION("DIGITAL INPUT driver");
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL");
這是一個字符驅(qū)動的實(shí)現(xiàn),在真實(shí)項(xiàng)目中,大部分驅(qū)動基本已經(jīng)被芯片廠商給實(shí)現(xiàn)了,但是一些特殊項(xiàng)目的自定義需求,往往就需要去實(shí)現(xiàn)自己的驅(qū)動。
編譯部署
運(yùn)行以下命令:
petalinux-config -c rootfs
進(jìn)入modules,使能剛剛創(chuàng)建的模塊,退出保存。
運(yùn)行下面的命令進(jìn)行編譯:
petalinux-build
最終在工程目錄下,搜索di-drv.ko,就得到這個驅(qū)動的內(nèi)核模塊文件了,拷貝到目標(biāo)板的某個文件夾下,運(yùn)行下面的命令裝載就完成了:
insmod di-drv.ko
這樣在/dev下就會發(fā)現(xiàn)新增一個di-drv設(shè)備。
當(dāng)然也可以直接將該驅(qū)動放進(jìn)內(nèi)核里,這就需要在內(nèi)核代碼樹里,添加文件了,這個思路之前有分享過。
總結(jié)一下
字符設(shè)備是做驅(qū)動開發(fā)比較容易掌握的驅(qū)動類型,也是大多數(shù)項(xiàng)目中,需要自己動手寫的最多的驅(qū)動類型。所以還是應(yīng)該掌握它。才能實(shí)現(xiàn)不同的項(xiàng)目需求。至于用戶空間怎么訪問這個設(shè)備,這里就不贅述了,一個文件打開操作,再來一個讀取操作就完事了。