需求
Ubuntu 18.04 基于VM虚拟机。参考suspendmonitor,实现USB插拔监控驱动+APP用户应用程序。
- 参考 suspend_monitor,实现USB 拔插监控驱动+应用程序
- 编写一个kernel module, 注册usb callback, 用于监控USB设备的插拔情况
- 提供 /proc/usb_monitor 设备节点,提供读取接口,功能开关接口。
- Userspace 应用程序,读取/proc/usb_monitor设备节点数据,以RingBuffer形式存储插拔数据 (最多512条),数据格式自定义,但必须包括:USB设备名称+插入时间+拔离时间。
思路
- 注册usb的插拔的回调函数,设置notifier_block结构体并初始化此结构体,设定.notifier_call函数地址,通过usb_register_notify,注册函数,并编写回调函数信息。
// USB设备的通知回调函数
static int usb_notify(struct notifier_block* self, unsigned long action, void* dev) {
struct usb_device* usb_dev = (struct usb_device*)dev;
int index;
mutex_lock(&monitor->usb_monitor_mutex); // 互斥锁
if (monitor->enable_usb_monitor == 0) {
LOGE("%s:usb monitor is disable\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return 0;
}
switch (action) {
// 状态判断
case USB_DEVICE_ADD:
// USB设备插入时进行添加
index = write_in_message(1, usb_dev);//拿到index
LOGI("### The add device name is %s %d\n", monitor->message[index].name,
monitor->usb_message_count);
printk(KERN_INFO "### The add device name is %s %d\n", monitor->message[index].name,
monitor->usb_message_count);
wake_up_interruptible(&monitor->usb_monitor_queue); // 唤醒中断,
break;
case USB_DEVICE_REMOVE:
index = write_in_message(0, usb_dev);
LOGI("### The remove device name is %s %d\n", monitor->message[index].name,
monitor->usb_message_count);
printk(KERN_INFO "### The remove device name is %s %d\n", monitor->message[index].name,
monitor->usb_message_count);
wake_up_interruptible(&monitor->usb_monitor_queue);
break;
}
mutex_unlock(&monitor->usb_monitor_mutex);//解锁
return NOTIFY_OK;
}
static struct notifier_block usb_nb = {
.notifier_call = usb_notify, // 设定USB通知回调函数
};
- 通过字符设备驱动方法,让用户和驱动程序交互。使用proc_create的方法,创建/proc/usb_monitor文件。为实现交互同时创建read和write函数。主要参考suspend_monitor中的部分。到此,最终实现也只要在用户程序中读取/proc/usb_monitor设备节点即可,并通过驱动提供的接口,控制驱动的开关。
static int init_module_(void) {
monitor = kzalloc(sizeof(struct usb_monitor_t), GFP_KERNEL);/*初始化申请内存空间*/
if (!monitor) {
LOGE("%s:failed to kzalloc\n", TAG);
return -ENOMEM;
}
// 初始化变量
monitor->usb_message_count = 0;
monitor->usb_message_index_read = 0;
monitor->usb_message_index_write = 0;
monitor->enable_usb_monitor = 1;
monitor->flag = "init_module_ is inited !\n";
proc_create("usb_monitor", 0644, NULL, &usb_monitor_fops); /*创建proc/usb_monitor*/
init_waitqueue_head(&monitor->usb_monitor_queue); /*初始化等待队列*/
mutex_init(&monitor->usb_monitor_mutex);/*初始化互斥锁*/
printk(KERN_INFO "Init USB hook.\n");
usb_register_notify(&usb_nb);/*注册回调函数*/
return 0;
}
- 功能开关接口的具体实现
static long usb_monitor_ioctl(struct file* filp, unsigned int cmd,
unsigned long arg)
{
void __user* ubuf = (void __user*)arg;
unsigned char status;
LOGI("%s:%s\n", TAG, __func__);
mutex_lock(&monitor->usb_monitor_mutex);
switch (cmd) {
case CMD_GET_STATUS: /*返回当前驱动的开关状态*/
LOGI("%s:ioctl:get enable status\n", TAG);
if (monitor->enable_usb_monitor == 0)
status = 0x00;
else
status = 0xff;
LOGI("%s:ioctl:status=0x%x\n", TAG, status);
/*拷贝数据*/
if (copy_to_user(ubuf, &status, sizeof(status))) {
LOGE("%s:ioctl:copy_to_user fail\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return -EFAULT;
}
break;
default:
LOGE("%s:invalid cmd\n", TAG);
mutex_unlock(&monitor->usb_monitor_mutex);
return -ENOTTY;
}
mutex_unlock(&monitor->usb_monitor_mutex);
return 0;
}
static ssize_t usb_monitor_write(struct file* filp, const char __user* buf,
size_t size, loff_t* ppos) //接收用户程序的命令
{
char end_flag = 0x0a, cmd;
LOGI("%s:%s\n", TAG, __func__);
/*仅支持size=2 */
if (size != 2) {
LOGE("%s:invalid cmd size: size = %d\n", TAG, (int)size);
return -EINVAL;
}
/*拷贝用户态数据*/
if (copy_from_user(monitor->write_buff, buf, size)) {
LOGE("%s:copy_from_user error!\n", TAG);
return -EFAULT;
}
if (monitor->write_buff[1] != end_flag) {
LOGE("%s:invalid cmd: end_flag != 0x0a\n", TAG);
return -EINVAL;
}
cmd = monitor->write_buff[0];
mutex_lock(&monitor->usb_monitor_mutex);
switch (cmd) { //控制驱动开关状态
case '0':
monitor->enable_usb_monitor = 0;
LOGI("%s:disable usb monitor\n", TAG);
break;
case '1':
monitor->enable_usb_monitor = 1;
LOGI("%s:enable usb monitor\n", TAG);
break;
default:
LOGE("%s:invalid cmd: cmd = %d\n", TAG, cmd);
mutex_unlock(&monitor->usb_monitor_mutex);
return -EINVAL;
}
mutex_unlock(&monitor->usb_monitor_mutex);
return size;
}
- Userspace 应用程序,读取/proc/usb_monitor设备节点数据,以RingBuffer形式存储插拔数据 (最多512条),数据格式自定义,但必须包括:USB设备名称+插入时间+拔离时间。
static void* DoUsbMonitor(void* arg) {
int ret;
ssize_t lenth = 0, i = 0;
struct epoll_event epev;
UsbInfo deviceinfo;
UsbMonitorDevice* device = (UsbMonitorDevice*)arg;
char* buf = device->getBuffer();
device->FifoReset(BUFFER_SIZE);
while (1) {
printf("usb_monitor epoll_wait... \n");
ret = epoll_wait(device->getepollfd(), &epev, MAX_EPOLL_EVENTS, -1); //epoll等待
if (ret == -1 && errno != EINTR) {
printf("usb_monitor epoll_wait failed; errno=%d\n", errno);
return (void*)-1;
}
lenth = read(device->getFd(), buf, KERNEL_DATA_LENG);
if (lenth > 0) {
printf("read length is %d\n", lenth);
//8 字节的 kernel time
for (i = 0; i < 8; i++) {
data_info.kernel_time[i] = buf[i];
printf("kernel_time[%d] = 0x%x \n", i, data_info.kernel_time[i]);
}
ret = pthread_mutex_lock(&data_mutex); //get lock 互斥锁
if (ret != 0) {
printf("Error on pthread_mutex_lock(), ret = %d\n", ret);
return (void*)-1;
}
// 记录插拔状态
data_info.status = buf[8];
// 记录USB名称
memcpy(data_info.name, buf + 9, lenth - 9);
ret = pthread_mutex_unlock(&data_mutex); //unlock 解锁互斥锁
if (ret != 0) {
printf("Error on pthread_mutex_unlock(), ret = %d\n", ret);
return (void*)-1;
}
device->AppendDatainfo(deviceinfo); //保存数据到ringbuffer
fifo_size = device->GetFifoSize();
if (data_info.status == 1) { //根据插拔状态打印信息
printf("USB %s -> plug In \n", data_info.name);
}
else {
printf("USB %s -> plug Out \n", data_info.name);
}
printf("\n");
if (isEmpty) {
for (i = 0; i < isEmpty; i++)
pthread_cond_signal(&fifo_nonzero);
}
printf("Current BufferSize = %ld \n", fifo_size);
}
}
}
买了专栏的用户可以私信我要源码 thanks.