韓賢忠 胡業火
摘 要: 本文以PC-6325A板卡Linux系統下的驅動開發為例介紹了PC-6325A板卡信息及工作原理,linux內核編譯加載、常用函數方法實現等基本編程技術。可為Linux系統下驅動程序開發人員提供一定參考。
關鍵詞: Linux;驅動程序
1 引言
由于Linux系統具有運行安全穩定、功能強大、獲取方便等眾多優點,逐漸被眾多開發人員所使用。但市場上Linux發行版本多,很多硬件設備缺少對應版本的驅動程序,需要自行開發。
本文以PC-6325A板卡開發為例,介紹了linux內核編譯加載、常用函數方法實現等基本編程技術,在后續驅動程序開發中可以此為參考。
2 硬件設備分析
PC-6325A模入接口卡適用于具有ISA總線的PC系列微機,具有很好的兼容性,CPU從目前廣泛使用的64位處理器到早期的16為處理器均可使用,操作系統也適用于MS-DOS、Windows系列、Unix等多種操作系統以及專業數據采集分析系統labVIEW等軟件環境。以下對PC-6325A板卡進行詳細介紹。
2.1 工作原理
PC-6325A板卡主要由模擬多路開關電路、放大器電路、模數轉換電路、接口控制邏輯電路、光電隔離電路及DC/DC電源電路組成。
2.1.1 模擬多路開關電路
模擬多路開關由4片8選1模擬開關芯片等組成,通過KJ1和KJ2跨接插座可以選擇32路單端或16路雙端輸入方式,并將選中的信號送入差分放大器處理。
2.1.2 模數轉換電路
PC-6325A卡選用新一代A/D器件ADS7808作為模數轉換器件。ADS7808內部自帶采保和精密基準電源。A/D轉換可以由程序啟動,也可由外部觸發信號啟動。A/D轉換結束標志可以由程序查詢檢出,也可通過中斷方式通知CPU處理。
2.1.3 接口控制邏輯電路及光隔電路
接口控制邏輯電路用來產生與各種操作有關的控制信號。光隔電路采用6N137高速光耦對系統總線與模擬信號之間進行光電隔離,以避免相互間的干擾。
2.1.4 DC/DC電源電路
DC/DC電源電路有電源模塊及相關的濾波元件組成。該電源模塊的輸入電壓為+5V,輸出電壓為與原邊隔離的±15V和+5V,原付邊之間隔離電壓可達1500V。
3 驅動程序設計
3.1 編譯內核
設備驅動屬于linux內核的部分在編寫Linux設備驅動前需要對Linux操作系統內核進行編譯。本次PC-6325板卡驅動開發調試采用2.6.23版本內核。
編譯步驟如下:
1.下載并解壓Linux內核一般內核源碼放在/usr/src目錄下。
2.清除從前編譯內核時殘留的.o文件和不必要的關聯:
cd /usr/src/linux
make mrproper
確保源代碼目錄下沒有不正確的.o文件和文件依賴關系,執行該命令后,內核選項會回到默認的狀態下。如果為下載的內核源碼,且為第一次編譯可跳過該步驟。
3.配置內核,修改相關參數:
圖形界面下,make xconfig;
字符界面下, make menuconfig;
在內核配置菜單中正確設置內核選項保存退出。
4.正確設置關聯文件:
make dep
根據上一步所選擇的選項,建立文件的依賴關系。
5.編譯內核:
對于大內核,make bzlmage
對于小內核,mkae zlmage
6.編譯模塊:
make modules
編譯可加載模塊(即內核選項中選擇為M的選項)。
7.安裝模塊:
make modules_install
將編譯好的modules拷貝到/lib/modules下(該步驟需要管理員權限)。
3.2 編寫驅動程序
內核編譯完成后就可以進行驅動程序代碼編寫了,但在此之前需要了解一下Linux系統的一個基本概念,內核空間和用戶空間。模塊運行在內核空間用于擴展內核功能,應用程序運行在用戶空間。內核空間與用戶空間可理解為兩種運行模式,有著不同的優先級及自己的內存映射。相應的在代碼編寫中也會有內核空間編程及用戶空間編程的區別。本文將以PC-6325A板卡驅動開發中的具體代碼為例對驅動程序中內核空間及用戶空間較為常用的函數方法進行詳細介紹。
3.2.1 初始化和關閉
1.初始化代碼如下:
static int __init pc6325_init (void){
register_chrdev(MAJOR_NUM, "pc6325", &pc6325;_fops);
gPc6325Dev = kmalloc(sizeof(struct pc6325_dev), GFP_KERNEL);
init_MUTEX(&gPc6325Dev-;>sem); /*初始化信號量*/
return 0;
}
module_init(pc6325_init);
初始化函數聲明為static,該函數僅在初始化期間使用。模塊裝載之后,模塊裝載器將初始化函數丟掉,并釋放函數所占用的內存。
module_init用于說明內核初始化函數的位置。如果沒有這個定義,初始化函數將無法被調用。
2.清除函數代碼如下:
static void __exit pc6325_exit (void){
unregister_chrdev(MAJOR_NUM, "pc6325");
}
module_exit(pc6325_exit);
清除函數在模塊移除前注銷接口并返回系統中的所有資源。清除函數無返回值,聲明為void清除函數用于模塊卸載,在模塊卸載或者系統關閉時調用。
3.2.2 數據讀取與發送
1.讀取數據代碼如下
static ssize_t pc6325_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct pc6325_dev *dev = filp->private_data; /*獲得設備結構體指針*/
if (down_interruptible(&dev-;>sem)) /* 獲得信號量 */
{
return - ERESTARTSYS;
}
dev->mem[0]=inb(dev->address+p);
copy_to_user(buf, &(dev->mem[0]), count);
up(&dev-;>sem);
return ret;
}
該函數用來從設備讀取數據,函數指針被賦予NULL值時,將導致reed系統調用出錯并返回-EINVAL(非法參數)。函數返回非負值表示成功讀取的字節數。對于該方法,參數filp是文件指針,參數count是請求傳輸的數據長度。參數buff是用戶空間的指針,指向用戶空間的緩沖區,保存要寫入的數據,或者是一個存放新讀入數據的空緩沖區。最后的offp是一個指向長偏移量類型的對象指針,用于指明用戶在文件中進行存取操作的位置。
2.發送數據函數如下:
static ssize_t pc6325_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
unsigned char vData=0;
struct pc6325_dev *dev = filp->private_data; /* 獲得設備結構體指針 */
if( count<3 ){
return;
}
if (down_interruptible(&dev-;>sem)) /* 獲得信號量 */
{
return - ERESTARTSYS;
}
copy_from_user(dev->mem+p,buf,count);
if( count==3 && dev->mem[0]==0xED ){
dev->address=dev->mem[1]*0x100+dev->mem[2];
pc6325_set_base_address(dev->address);
}else if( count==3 && dev->mem[0]==0xCD ){
dev->offset=dev->mem[1];
vData=dev->mem[2];
outb(vData,dev->address+dev->offset);
}
up(&dev-;>sem); /* 釋放信號量 */
return ret;
}
向設備發送數據。如果返回值非負,則表示成功寫入的字節數。其用法及參數與read方法一致,在此不再贅述。
3.2.3 open和release方法
1.open方法:
int pc6325_open(struct inode *inode, struct file *filp)
{
filp->private_data = gPc6325Dev;
return 0;
}
Open方法提供給驅動程序初始化的能力,從而為以后的操作完成初始化的準備。在多數驅動程序中,open完成如下工作:
a)檢查設備特定的錯誤;
b)如果設備是首次打開,則對其進行初始化;
c)必要時,更新f_op指針;
d)非配并填寫置于filp->private_data里的數據結構[1]。
2.release方法:
int pc6325_release(struct inode *inode, struct file *filp)
{
struct pc6325_dev *dev = filp->private_data;
pc6325_release_base_address(dev->address);
//printk(KERN_ALERT "address release");
return 0;
}
release方法的作用于open正好相反。該方法通常完成以下任務。
a)釋放有open分配的、保存在filp->private_data中的所有內容;
b)在最后一次關閉操作時關閉設備。
3.2.4 file_operations結構
代碼如下:
struct file_operations pc6325_fops={
read: pc6325_read,
write: pc6325_write,
open: pc6325_open,
release: pc6325_release,
owner: THIS_MODULE,
};
file_operations結構用于建立驅動程序與設備編號之間的連接。主要用于實現系統調用。結構中的每一個字段都指向驅動程序中特定操作的函數,對于不支持的操作,對應的字段可設置為NULL值。
3.2.5 模塊加載與卸載
1. 模塊加載
# Create the device nodes (up to 10 by default)
echo -n "Creating device nodes........... "
rm -f ${path}/${name}*
mknod ${path}/${name} c $major 226
#mknod ${path}/${name} c 226 0
# Create additional nodes for non-service driver
if [ "${bServiceDriver}" == "0" ]; then
mknod ${path}/${name}-0 c $major 000
mknod ${path}/${name}-1 c $major 001
mknod ${path}/${name}-2 c $major 002
mknod ${path}/${name}-3 c $major 003
mknod ${path}/${name}-4 c $major 004
mknod ${path}/${name}-5 c $major 005
mknod ${path}/${name}-6 c $major 006
mknod ${path}/${name}-7 c $major 007
mknod ${path}/${name}-8 c $major 008
mknod ${path}/${name}-9 c $major 009
fi
chmod 777 $path
上述代碼中rm -f ${path}/${name}*的作用為卸載之前的模塊以釋放空間在模塊卸載中也會用到,之后的代碼為進行模塊加載。其中mknod命令用于創建設備文件。最后的chmod 777 $path表示賦予讀、寫以及運行的權限。
2.模塊卸載
echo -n "Clear existing device nodes..... "
rm -f $path/${name}*
echo "Ok (${path}/${name})"
卸載模塊代碼較為簡單使用rm -f $path/${name}*指令卸載即可。
4.結論
Linux系統對于開發人員而言有著眾多的優點,但市場上Linux發行版本多,很多硬件設備缺少對應版本的驅動程序,需要自行開發。本文以PC-6325A板卡驅動開發為例介紹了PC-6325A板卡信息及工作原理,linux內核編譯加載、常用函數方法實現等基本編程技術??蔀長inux系統驅動開發人員提供參考。
參考文獻
[1]LINUX設備驅動程序第三版,中國電力出版社,2006年1月,魏永明,耿岳,鐘書毅 譯.
[2]PC-6325A板卡用戶手冊.