Linux简单设备驱动(2): file_operations的write、read、ioctl驱动及Android应用层开发验证

前言

本文一部分代码继承了上一篇《Linux简单设备驱动(1):使用IO内存操作GPIO–LED》相关的驱动代码,并添加了file_operations相关函数。
本人又对Android应用程序编程有一定的理解,因此便借用Android应用层来验证本驱动程序的成功与否。
本文只是初步验证此途径的可行性,驱动代码中很多都不够严谨,若要严谨的话估计代码更长了。。。无关紧要,只是学习,当作更容易理解的总结吧。

硬件方面

本文依旧使用的是迅为电子的三星exynos4412处理器开发板。

总体功能

在Android应用程序中,点击Button控制LED亮灭,文本框可输入文字并发送至linux驱动程序中,最后可点击读取Button,将该文字从linux驱动程序层中取回来,并显示。

linux驱动层开发

下面代码比较典型了,也在上一篇《Linux简单设备驱动(1):使用IO内存操作GPIO–LED》说到了,这里就把代码放在这。主要是完成模块加载,动态注册设备,初始化GPIO口,连接file_operations结构体。

/* 自定义函数 */
static void gpio_init(void)
{
unsigned int state;
//GPL2BASE寄存器向IO内存的映射
if(request_mem_region(GPL2BASE_PA,GPL2LEN_PA,DRIVER_NAME)==NULL) {
    goto GPL2BASE_FAIL;
}
else{
    printk(KERN_EMERG "request_mem_region succeed!\n");
}
//GPL2BASE IO内存的重映射
p1=ioremap(GPL2BASE_PA,GPL2LEN_PA);
if(p1==NULL){
    goto GPL2IOMEM_FAIL;
}
else {      
    GPL2=(GPIO_TypeDef *)p1;
    printk(KERN_EMERG "GPL2 ioremap succeed!\n");
}
state=ioread32(&GPL2->CON);
iowrite32(state&0xfffffff1,&GPL2->CON);//配置IO口输出
state=ioread32(&GPL2->DAT);
iowrite32(state|0x00000001,&GPL2->DAT);//配置IO输出高电平
return;
GPL2IOMEM_FAIL:
printk(KERN_EMERG "GPL2 ioremap failed!\n");
release_mem_region(GPL2BASE_PA,GPL2LEN_PA);
GPL2BASE_FAIL:
printk(KERN_EMERG "request_mem_region failed!\n");
return;
}

/* 自定义函数 */
static void gpio_deinit(void)
{
iounmap(p1);
release_mem_region(GPL2BASE_PA,GPL2LEN_PA);
}

/* 自定义函数 */
static void setup_cdev(struct scull_dev *dev, int index)
{
int err;
cdev_init(&dev->cdev,&my_fops);
dev->cdev.owner=THIS_MODULE;
dev->cdev.ops=&my_fops;
err=cdev_add(&dev->cdev,mdev_t,1);
if(err!=0){
    printk(KERN_EMERG "cdev_add err=%d\n",err);
}
else{
    printk(KERN_EMERG "cdev_add succeed!\n");
}
}
static int __init mchrdev_init(void)
{
int ret=0;
ret=alloc_chrdev_region(&mdev_t, 0, 1, CHRDEV_NAME);//分配设备号
if(ret!=0){
    printk(KERN_ALERT "alloc_chrdev_region failed. %d \n",ret);
    goto fail1;
}
else{
    printk(KERN_ALERT "alloc_chrdev_region succeed! %d,%d\n",MAJOR(mdev_t),MINOR(mdev_t));
}
//注册设备class
myclass=class_create(THIS_MODULE,CLASS_NAME);

//字符设备分配内存,这里先这么放着,本文用不上
mydevice=kmalloc(sizeof(struct scull_dev),GFP_KERNEL);
if(!mydevice){
    printk(KERN_ALERT "kmalloc failed!/n");
    ret=-ENOMEM;
    goto fail2;
}
else{

}
memset(mydevice,0,sizeof(struct scull_dev));//清空数据
//字符设备注册
setup_cdev(mydevice, 0);
//生成设备节点
device_create(myclass,NULL,mdev_t,NULL,DEVICE_NAME);
gpio_init();
printk(KERN_ALERT "mchrdev init!/n");
return ret;

fail2:
unregister_chrdev_region(mdev_t,1);
fail1:
return ret;
}

static void __exit  mchrdev_exit(void)
{

cdev_del(&mydevice->cdev);
/*下面这两个释放函数有顺序要求!!*/
device_destroy(myclass,mdev_t);
class_destroy(myclass);

gpio_deinit();

kfree(mydevice);
unregister_chrdev_region(mdev_t,1);
printk(KERN_ALERT "mchrdev exit!/n");
}

module_init(mchrdev_init);
module_exit(mchrdev_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("tarkelc");

下面就是字符驱动用的file_operations结构体,很简单。这里本想编写一个SPI驱动的,后面发现得先打通读写关系的任督二脉,就懒得改过来了。

struct file_operations my_fops={
.owner = THIS_MODULE,
.read = myspi_read,
.write = myspi_write,
.open = myspi_open,
.release = myspi_release,
.unlocked_ioctl = myspi_ioctl,
};

这里的read函数指针,便对应着应用程序的read函数;同理write对应write;open对应open;release对应close;unlocked_ioctl对应ioctl。这里主要总结的是读写函数的操作。
读取操作方面,代码如下:

static ssize_t myspi_read(struct file *f, char __user *buf, size_t len, loff_t *loff)
{
    int ret=0;
    ret=copy_to_user(buf,buffer,len);
    if(ret!=0){
        return -1;
    }
    return len;
}

驱动层的read函数中间两个传入的参数(buf和len)是从用户层中传下来的,其他两个用不到,等用到了再去探究他们的用途把。
linux内核对驱动层与用户层的数据读写操作有着严格的限制,若应用层的指针能够对驱动层的数据进行任务读取,会造成安全性的问题。本人曾经天真地这么操作过,结果显而易见:直接GG。

for(i=0;i<len;i++){
    buf[i]=buffer[i];
}//buf是用户层传下来的char指针,buffer是驱动层的char数据指针

对于这种情况,linux内核提供了copy_to_user()和copy_from_user()两种函数,供应用层和驱动层的数据交互,头文件是asm/uaccess.h。
写入函数也一样,这里不多介绍。。

static ssize_t myspi_write(struct file *f, const char __user *buf, size_t len, loff_t *loff)
{
    int ret;
    ret=copy_from_user(buffer,buf,len);
    if(ret!=0){
        return -1;
    }
    return len;
}

至此驱动层就搞定了。下面说一下Android方面应用层的开发吧。
//===================分割线=========================

JNI层开发

Android应用程序开发采用的是java开发,而传统的linux应用程序采用的是C或C++开发,怎么将二者的任督二脉打通,靠的就是JNI层。有关JNI层更多的知识,百度下就出来了,这里不废话。
JNI用的是eclipse开发,通过一系列的开发环境搭建,我们能够让开发环境给我们自动生成JNI层的头文件,具体怎么搭建大家还是百度把。
这里写图片描述
上图是Android.mk文件的相关内同,右边代码中的红框的搭建好开发环境后自动生成的代码,篮框是自己添加上去的,为什么要这么加,本人也不怎么明白,只知道不这么加的话,左边的篮框内容就无法生成,然后自然就出错。。。。
然后左边的ling_takrelc_myledtest_myleds.h的头文件就是JNI自动生成的,不用动它。
还有就是LOCAL_SRC_FILES :=MyledsTest.cpp内容自然就对应这左边jni文件夹下的MyledsTest.cpp文件。
而LOCAL_MODULE :=MyledsTest是什么鬼?提前说一下,java层要调用JNI层的c程序,需要有下面这个代码:

static{
    System.loadLibrary("MyledsTest");
}

所以MyledsTest就是对应着”MyledsTest”。若不爽在这个代码中任意该System.loadLibrary(“我想随便改”),那么就会在运行中直接出现空指针变量的报错,GG。
下面贴一下MyledsTest.cpp文件的代码

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
#include <termios.h>
#include <android/log.h>
#include <sys/ioctl.h>
#include "ling_tarkelc_myledstest_Myleds.h"

int fd=0;

JNIEXPORT jint JNICALL Java_ling_tarkelc_myledstest_Myleds_open(JNIEnv *env, jobject obj)
{
    //if(fd<=0)fd = open("/dev/myleds", O_RDWR|O_NDELAY|O_NOCTTY);
    if(fd<=0)fd = open("/dev/tarkelc_learn", O_RDWR|O_NDELAY);
    if(fd <=0 )__android_log_print(ANDROID_LOG_INFO, "serial", "open /dev/outsize_gpio Error");
    else __android_log_print(ANDROID_LOG_INFO, "serial", "open /dev/outsize_gpio Sucess fd=%d",fd);
    return fd;
}

JNIEXPORT jint JNICALL Java_ling_tarkelc_myledstest_Myleds_close(JNIEnv *env, jobject obj)
{
    if(fd > 0)close(fd);
    return 0;
}

JNIEXPORT jint JNICALL Java_ling_tarkelc_myledstest_Myleds_ioctl(JNIEnv *env, jobject obj, jint en, jint num)
{
    jint ret;
    ret=ioctl(fd, en, num);
    return ret;
}

JNIEXPORT jstring JNICALL Java_ling_tarkelc_myledstest_Myleds_helloworld(JNIEnv *env, jobject obj)
{
    return (*env).NewStringUTF("wokao!!!!");
}

JNIEXPORT jint JNICALL Java_ling_tarkelc_myledstest_Myleds_read(JNIEnv *env, jobject obj, jbyteArray buffer, jint length)
{
    jint ret;
    jbyte *pBuffer=env->GetByteArrayElements(buffer,NULL);
    ret=read(fd,pBuffer,length);
    return ret;
}

JNIEXPORT jint JNICALL Java_ling_tarkelc_myledstest_Myleds_write(JNIEnv *env, jobject obj, jbyteArray msg, jint length)
{
    jint ret;
    jbyte pBuffer[length];
    env->GetByteArrayRegion(msg,0,length,pBuffer);
    ret=write(fd,pBuffer,length);
    return ret;
}

熟悉linux应用层开发的童鞋就上面的头文件应该很熟悉,上面的代码就是用来打通java调用C语言代码任督二脉所用。以Java_ling_tarkelc_myledstest_Myleds_write(JNIEnv *env, jobject obj, jbyteArray msg, jint length)这个函数为例,函数名长得可怕,没办法就是这么玩的,首先java开头必不可少,下面就对应这java的包名,然后是类名,最后是方法名。

Android应用程序层开发

java想调用C语言的代码,需要定义下面一个类,当然也可能有其他方法。

public class Myleds {
    public native int open();
    public native int close();
    public native int ioctl(int num, int en);
    public native String helloworld();
    public native int read(byte[] buf,int len);
    public native int write(byte[] msg,int len);
}

上面的每一个方法,便回应着jni层MyledsTest.cpp里面的函数。Android其他代码在这里就不贴出来了,付一个共享的链接在此,0分欢迎大家去下载参考
Android效果图片如下:由于开发板中的Android好像没有截屏功能,这里就随便拍下来了。在下面输入所要发送的内容,点击写入测试按钮,然后通过点击读取测试按钮就可以将刚才写入的内容读回来,看起来有点搓,但也证明了这个读写的任督二脉已经打通。
这里写图片描述

从驱动层到Android应用的调用操作

最后还要补充一点关键的东西:
1.处于学习阶段调试代码的方面性,这里采用的是动态去加载驱动模块,即在linue控制台中用insmod xxx.ko对编译好的驱动文件进行加载
2.加载完驱动文件后还不够,在/dev目录下,找到我们编写驱动的设备节点,然后chmod 777 XXX赋给该设备节点最高的权限,这样我们在Android程序中才能够通过open函数进行打开,否则是打不开的。
3.对于上述两点的操作,在每次关开机后都要执行一次。
4.若不想这么复杂的操作,经本人百度了一下,不仅需要将驱动模块加入kernel内核编译中然后对内核重新烧写进去,好像还需要修改Android镜像文件里的什么脚本使该设备节点得到权限。本人只是学习阶段,若后面有这方面的应用再说吧。。。。。。。。。

最后

有了ioctl、read、write调用的基础,在下一篇linux简单设备驱动(3)中就要来个真格的驱动开发了———————利用IO内存在寄存器层面开发UART驱动!

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值