驱动与用户层通信之ioctl的使用
一、 驱动中注册netdev
驱动中注册net_device_ops全局变量结构体:
static const struct net_device_ops myNetDeviceOptions = {
.ndo_open = ...,
.ndo_stop = ...,//等号左侧是net_device_ops结构体里的函数名
.ndo_do_ioctl = handle_ioctl,//等号右侧是驱动中对应的函数名
.ndo_start_xmit = ...,//利用这个结构体绑定了驱动中的函数
... ... ... ...
//驱动需要使用的net device的相关函数需要注册,不使用的就无需列在这个结构体中
};
此外,还需将net_device_ops注册给上层:
在cfg80211初始化的时候,创建了网络设备,在这时候,驱动将这个ops函数集赋值给了驱动对应的网络设备。
struct net_device *dev;
dev->netdev_ops = &myNetDeviceOptions ;
之后,用户层通过调用ioctl传到驱动层以后,就可以调用已注册的这个handle_ioctl函数来处理ioctl通信了。
二、 驱动中的处理ioctl通信的函数
handle_ioctl代码:
static int handle_ioctl(struct net_device *dev, struct ifreq *req, int command)
{
int ret = 0;
///TODO: add ioctl command handler later
switch(command)
{
case XXX://根据command值的不同,可以有很多条ioctl的通信通道
printk("XXX\n");
break;
... ... ... ...
case SIOCDEVPRIVATE+2:
printk("IOCTL \n");
parse_ioctl_cmd(dev, req);//具体处理分析command的自己写的逻辑函数
break;
default:
ret = FAILEDERR;
}
return ret;
}
//其中定义如下:
//int parse_ioctl_cmd(struct net_device *dev, struct ifreq *ifr)
三、 内核中相关定义规范
这里用的是rk3399 android 10的代码。
关于SIOCDEVPRIVATE定义见:~/rk3399-android-10/kernel/include/uapi/linux/sockios.h
/* Device private ioctl calls */
/*
* These 16 ioctls are available to devices via the do_ioctl() device
* vector. Each device should include this file and redefine these names
* as their own. Because these are device dependent it is a good idea
* _NOT_ to issue them to random objects and hope.
*
* THESE IOCTLS ARE _DEPRECATED_ AND WILL DISAPPEAR IN 2.5.X -DaveM
*/
#define SIOCDEVPRIVATE 0x89F0 /* to 89FF */
... ... ... ...
#define SIOCGIFFLAGS 0x8913 /* get flags */
此处,每一个设备都可以定义自己的ioctl命令字,命令编号的范围是SIOCDEVPRIVATE到SIOCDEVPRIVATE + 15。具体是哪个编号不是定死的,只要驱动里与应用程序里的编号能对应上即可。
关于ifreq结构体定义见:~/rk3399-android-10/kernel/include/uapi/linux/if.h
#if __UAPI_DEF_IF_IFREQ
struct ifreq {
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#endif /* __UAPI_DEF_IF_IFREQ */
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
由结构体定义+define结合来看,可知ioctl传进来的命令是存储在ifreq.ifr_data里的。
注:在parse_ioctl_cmd中可以用ifr->ifr_data来访问,要注意在驱动中想要把ifr->ifr_data中的数据操作,需要用到copy_from_user和copy_to_user。
四、 驱动中存取ifreq中数据
- 取出数据
struct my_command cmd_value;
/* my_command 是自定义的结构体,和用户层的对应上就可以。
例如:
typedef struct my_command{
char *buf;
int len;
... ... ... ... //其他你需要用到的参数
} my_command;
*/
copy_from_user(&cmd_value, ifr->ifr_data, sizeof(my_command));//ifr是传进来的req
... ... ... ...
char* cmd_str = NULL;
int buf_size = max(cmd_value.len ,MAX_SIZE);//buf_size是收到的包的大小
cmd_str = kmalloc((buf_size + 1), GFP_KERNEL);
copy_from_user(cmd_str, cmd_value.buf, cmd_value.len);
cmd_str[buf_size + 1] = '\0';
- 放入数据
//将需要返回给用户层的值存到cmd_str中。
copy_to_user(cmd_value.buf, cmd_str, 8);//即更新到ifr->ifr_data.buf里
//copy_to_user最后一个参数是传回的值的bytes个数
//这里cmd_str中填了两个int数,故而是8。
//将返回值填入传过来的ifr结构里后,
//在驱动处理ioctl通信的函数返回给上层后,
//上层会把这个更新了ifr_data.buf的ifr返回给用户空间
五、 用户层的软件代码:
- 主函数:
int main(int argc, char *argv[])
{
... ... ... ...
char* inf = "wlan0";//通过这个接口名称来建立通信线路,可通过ifconfig查看
send_cmd_to_driver(inf, argc, argv);//这里面是运行本程序时的命令行参数
... ... ... ...
return 0;
}
- 发送指令+接收结果的处理函数
int send_cmd_to_driver(const char* inf, int argc, char *argv[])
{
int sock;
struct ifreq ifr;
int ret = 0;
char buf[MAX_SIZE];//MAX_SIZE是自定义的传送指令buffer最大的大小
struct my_command command;//自定义的结构体
/* my_command 是自定义的结构体,和用户层的对应上就可以。
例如:
typedef struct my_command{
char *buf;
int len;
... ... ... ... //其他你需要用到的参数
} my_command;
*/
... ... ... ...
//创建socket柄
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
printf("wrong sock!\n");
return -1;
}
... ... ... ...
memset(&ifr, 0, sizeof(ifr));//初始化req,准备发送
strcpy(ifr.ifr_name, inf);//将网络接口名称填入req里
//通信前先检查下接口的状态
if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
printf("%s Could not read interface %s flags: %s",__func__, inf, strerror(errno));
return -1;
}
if (!(ifr.ifr_flags & IFF_UP)) {
printf("%s is not up!\n",inf);
return -1;
}
........
memset(&command, 0, sizeof(command));//初始化
memset(buf, 0, sizeof(buf));
........//一些对待传递的buf的赋值操作
command.buf = buf;
command.len= sizeof(buf);
ifr.ifr_data = (void*)&command;//将待发送数据存入ifr中
... ... ... ...
if ((ret = ioctl(sock, SIOCDEVPRIVATE+2, &ifr)) < 0) {//发给下层内核空间
printf("%s: error ioctl[SIOCDEVPRIVATE+2] ret= %d\n", __func__, ret);
return ret;
}
memcpy(&command, ifr.ifr_data, sizeof(struct my_command));
//通信返回的结果可以通过command.buf中传递回来。由驱动赋好值后,传到用户层来读取。
printf("i'm back: a= 0x%x b=%d\n", *(unsigned int *)command.buf, *(unsigned int *)&command.buf[4]);
//例如,这里这句输出意味着,buf中[0]-[3]这四个byte(也就是一个int的大小),存着第一个数值,用八进制输出。
//buf中[4]-[7]这四个byte,存第二个数,用十进制输出。
... ... ... ...
return ret;
}
总结
通过用户层ioctl的相关通信代码,无论这个用户软件是通过c编写并编译成可执行文件,还是通过native c++工程嵌入java代码工程中生成apk,均可成功地和内核中的驱动通信,这两种均已试验成功。