linux usb摄像头反复插拔出现Unable to handle kernel NULL pointer内核错误

现象描述

[ 3257.804953] usb 5-1: New USB device found, idVendor=0756, idProduct=0527, bcdDevice= 1.00
[ 3257.805008] usb 5-1: New USB device strings: Mfr=2, Product=1, SerialNumber=3
[ 3257.805018] usb 5-1: Product: MV-Medical
[ 3257.805027] usb 5-1: Manufacturer: MV
[ 3257.805035] usb 5-1: SerialNumber: MV0001
[ 3257.813216] uvcvideo: Found UVC 1.00 device USB-CAMERA
[ 3257.983766] usb 5-1: USB disconnect, device number 35
[ 3264.659381] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
[ 3264.672181] Mem abort info:
[ 3264.674998]   ESR = 0x96000006
[ 3264.678077]   Exception class = DABT (current EL), IL = 32 bits
[ 3264.684042]   SET = 0, FnV = 0
[ 3264.687125]   EA = 0, S1PTW = 0
[ 3264.690289] Data abort info:
[ 3264.693254]   ISV = 0, ISS = 0x00000006
[ 3264.697136]   CM = 0, WnR = 0
[ 3264.700203] user pgtable: 4k pages, 39-bit VAs, pgdp = 00000000a561b7d1
[ 3264.706827] [0000000000000000] pgd=000000006f92f003, pud=000000006f92f003, pmd=0000000000000000
[ 3264.715556] Internal error: Oops: 96000006 [#1] SMP
[ 3264.720431] Modules linked in: rknpu(O) bcmdhd
[ 3264.724886] Process C100A_RK3568 (pid: 620, stack limit = 0x00000000de471811)
[ 3264.732020] CPU: 0 PID: 620 Comm: C100A_RK3568 Tainted: G           O      4.19.172 #41
[ 3264.740018] Hardware name: evm-rk3568 base on Rockchip RK3568 EVB1 DDR4 V10 Board (DT)
[ 3264.748364] pstate: 80400009 (Nzcv daif +PAN -UAO)
[ 3264.753158] pc : usb_ifnum_to_if+0x48/0x64
[ 3264.757256] lr : usb_hcd_alloc_bandwidth+0x214/0x2c0
[ 3264.762219] sp : ffffff800b84b8b0
[ 3264.765532] x29: ffffff800b84b8b0 x28: ffffffc063f38018 
[ 3264.770844] x27: 00000000ffffffb9 x26: ffffffc0703f4808 
[ 3264.776157] x25: 0000000000000000 x24: ffffffc071c72000 
[ 3264.781469] x23: 0000000000000001 x22: ffffffc0703f4808 
[ 3264.786782] x21: 00000000ffffffb9 x20: ffffffc06f835800 
[ 3264.792094] x19: 0000000000000001 x18: 000000000000000a 
[ 3264.797406] x17: 0000000000000000 x16: 0000000000000000 
[ 3264.802717] x15: 0000000000000c50 x14: 30763a6273753d53 
[ 3264.808028] x13: 0000000000000196 x12: 0000000000000024 
[ 3264.813340] x11: ffffffc07eecb268 x10: 0000000000000a40 
[ 3264.818652] x9 : ffffff800b84b620 x8 : ffffffc06f8d6e60 
[ 3264.823963] x7 : 000000000000089b x6 : 0000000000049b80 
[ 3264.829274] x5 : ffffffc0702d3200 x4 : ffffffc07eecf3f0 
[ 3264.834585] x3 : ffffffc0703e90a0 x2 : ffffffc0703e90a8 
read ReadKeyVoltage error: Connection timed out
[ 3264.844139] x1 : 0000000000000001 x0 : 0000000000000000 
//中间省略一部分打印信息
[ 3265.989113] Call trace:
[ 3265.991567]  usb_ifnum_to_if+0x48/0x64
[ 3265.995318]  usb_hcd_alloc_bandwidth+0x214/0x2c0
[ 3265.999933]  usb_set_interface+0x1d0/0x324
[ 3266.004032]  uvc_video_enable+0x50/0x13c
[ 3266.007951]  uvc_start_streaming+0x30/0x6c
[ 3266.012051]  vb2_start_streaming+0x8c/0x150
[ 3266.016232]  vb2_core_streamon+0x168/0x174
[ 3266.020330]  vb2_streamon+0x6c/0x74
[ 3266.023813]  uvc_queue_streamon+0x38/0x58
[ 3266.027826]  uvc_ioctl_streamon+0x48/0x70
[ 3266.031839]  v4l_streamon+0x3c/0x4c
[ 3266.035324]  __video_do_ioctl+0x2d0/0x3f8
[ 3266.039336]  video_usercopy+0x3fc/0x668
[ 3266.043168]  video_ioctl2+0x3c/0x4c
[ 3266.046651]  v4l2_ioctl+0x50/0x74
[ 3266.049968]  vfs_ioctl+0x58/0x68
[ 3266.053198]  do_vfs_ioctl+0xb4/0x9d4
[ 3266.056777]  ksys_ioctl+0x50/0x80
[ 3266.060091]  __arm64_sys_ioctl+0x28/0x38
[ 3266.064010]  el0_svc_common.constprop.0+0xe8/0x168
[ 3266.068794]  el0_svc_handler+0x70/0x8c
[ 3266.072543]  el0_svc+0x8/0xc
[ 3266.075428] Code: 54000061 d2800000 14000006 f8408460 (f9400001) 
[ 3266.081513] ---[ end trace d992e9bf5856231b ]---

分析问题

很明显这个问题是由于出现访问空指针导致了内核出现崩溃

Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000

查看打印信息,定位到了这个函数

[ 3264.753158] pc : usb_ifnum_to_if+0x48/0x64
[ 3264.757256] lr : usb_hcd_alloc_bandwidth+0x214/0x2c0

这个是内核中的调用顺序,可能是尝试启用摄像头设备的视频流时出现问题(是由于分配 USB 带宽失败或设置 USB 接口时出现问题导致的。)

[ 3265.989113] Call trace:
[ 3265.991567]  usb_ifnum_to_if+0x48/0x64
[ 3265.995318]  usb_hcd_alloc_bandwidth+0x214/0x2c0
[ 3265.999933]  usb_set_interface+0x1d0/0x324
[ 3266.004032]  uvc_video_enable+0x50/0x13c
[ 3266.007951]  uvc_start_streaming+0x30/0x6c
[ 3266.012051]  vb2_start_streaming+0x8c/0x150
[ 3266.016232]  vb2_core_streamon+0x168/0x174
[ 3266.020330]  vb2_streamon+0x6c/0x74
[ 3266.023813]  uvc_queue_streamon+0x38/0x58
[ 3266.027826]  uvc_ioctl_streamon+0x48/0x70
[ 3266.031839]  v4l_streamon+0x3c/0x4c
[ 3266.035324]  __video_do_ioctl+0x2d0/0x3f8
[ 3266.039336]  video_usercopy+0x3fc/0x668
[ 3266.043168]  video_ioctl2+0x3c/0x4c
[ 3266.046651]  v4l2_ioctl+0x50/0x74
[ 3266.049968]  vfs_ioctl+0x58/0x68
[ 3266.053198]  do_vfs_ioctl+0xb4/0x9d4
[ 3266.056777]  ksys_ioctl+0x50/0x80
[ 3266.060091]  __arm64_sys_ioctl+0x28/0x38
[ 3266.064010]  el0_svc_common.constprop.0+0xe8/0x168
[ 3266.068794]  el0_svc_handler+0x70/0x8c
[ 3266.072543]  el0_svc+0x8/0xc

尝试定位解决

通过查看打印信息反馈的信息,相关源码如下所示
相关路径:
kernel/drivers/media/usb/uvc/uvc_driver.c
kernel/drivers/media/v4l2-core/v4l2-dev.c

kernel/drivers/usb/core/message.c

int usb_set_interface(struct usb_device *dev, int interface, int alternate)
{
	struct usb_interface *iface;
	struct usb_host_interface *alt;
	struct usb_hcd *hcd = bus_to_hcd(dev->bus);
	int i, ret, manual = 0;
	unsigned int epaddr;
	unsigned int pipe;

	if (dev->state == USB_STATE_SUSPENDED)
		return -EHOSTUNREACH;

	iface = usb_ifnum_to_if(dev, interface);
	if (!iface) {
		dev_dbg(&dev->dev, "selecting invalid interface %d\n",
			interface);
		return -EINVAL;
	}
	if (iface->unregistering)
		return -ENODEV;

	alt = usb_altnum_to_altsetting(iface, alternate);
	if (!alt) {
		dev_warn(&dev->dev, "selecting invalid altsetting %d\n",
			 alternate);
		return -EINVAL;
	}
	/*
	 * usb3 hosts configure the interface in usb_hcd_alloc_bandwidth,
	 * including freeing dropped endpoint ring buffers.
	 * Make sure the interface endpoints are flushed before that
	 */
	usb_disable_interface(dev, iface, false);

	/* Make sure we have enough bandwidth for this alternate interface.
	 * Remove the current alt setting and add the new alt setting.
	 */
	mutex_lock(hcd->bandwidth_mutex);
	/* Disable LPM, and re-enable it once the new alt setting is installed,
	 * so that the xHCI driver can recalculate the U1/U2 timeouts.
	 */
	if (usb_disable_lpm(dev)) {
		dev_err(&iface->dev, "%s Failed to disable LPM\n", __func__);
		mutex_unlock(hcd->bandwidth_mutex);
		return -ENOMEM;
	}
	/* Changing alt-setting also frees any allocated streams */
	for (i = 0; i < iface->cur_altsetting->desc.bNumEndpoints; i++)
		iface->cur_altsetting->endpoint[i].streams = 0;

	ret = usb_hcd_alloc_bandwidth(dev, NULL, iface->cur_altsetting, alt);
	if (ret < 0) {
		dev_info(&dev->dev, "Not enough bandwidth for altsetting %d\n",
				alternate);
		usb_enable_lpm(dev);
		mutex_unlock(hcd->bandwidth_mutex);
		return ret;
	}

	if (dev->quirks & USB_QUIRK_NO_SET_INTF)
		ret = -EPIPE;
	else
		ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
				   USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE,
				   alternate, interface, NULL, 0, 5000);

	/* 9.4.10 says devices don't need this and are free to STALL the
	 * request if the interface only has one alternate setting.
	 */
	if (ret == -EPIPE && iface->num_altsetting == 1) {
		dev_dbg(&dev->dev,
			"manual set_interface for iface %d, alt %d\n",
			interface, alternate);
		manual = 1;
	} else if (ret < 0) {
		/* Re-instate the old alt setting */
		usb_hcd_alloc_bandwidth(dev, NULL, alt, iface->cur_altsetting);
		usb_enable_lpm(dev);
		mutex_unlock(hcd->bandwidth_mutex);
		return ret;
	}
	mutex_unlock(hcd->bandwidth_mutex);

	/* FIXME drivers shouldn't need to replicate/bugfix the logic here
	 * when they implement async or easily-killable versions of this or
	 * other "should-be-internal" functions (like clear_halt).
	 * should hcd+usbcore postprocess control requests?
	 */

	/* prevent submissions using previous endpoint settings */
	if (iface->cur_altsetting != alt) {
		remove_intf_ep_devs(iface);
		usb_remove_sysfs_intf_files(iface);
	}
	usb_disable_interface(dev, iface, true);

	iface->cur_altsetting = alt;

	/* Now that the interface is installed, re-enable LPM. */
	usb_unlocked_enable_lpm(dev);

	/* If the interface only has one altsetting and the device didn't
	 * accept the request, we attempt to carry out the equivalent action
	 * by manually clearing the HALT feature for each endpoint in the
	 * new altsetting.
	 */
	if (manual) {
		for (i = 0; i < alt->desc.bNumEndpoints; i++) {
			epaddr = alt->endpoint[i].desc.bEndpointAddress;
			pipe = __create_pipe(dev,
					USB_ENDPOINT_NUMBER_MASK & epaddr) |
					(usb_endpoint_out(epaddr) ?
					USB_DIR_OUT : USB_DIR_IN);

			usb_clear_halt(dev, pipe);
		}
	}

	/* 9.1.1.5: reset toggles for all endpoints in the new altsetting
	 *
	 * Note:
	 * Despite EP0 is always present in all interfaces/AS, the list of
	 * endpoints from the descriptor does not contain EP0. Due to its
	 * omnipresence one might expect EP0 being considered "affected" by
	 * any SetInterface request and hence assume toggles need to be reset.
	 * However, EP0 toggles are re-synced for every individual transfer
	 * during the SETUP stage - hence EP0 toggles are "don't care" here.
	 * (Likewise, EP0 never "halts" on well designed devices.)
	 */
	usb_enable_interface(dev, iface, true);
	if (device_is_registered(&iface->dev)) {
		usb_create_sysfs_intf_files(iface);
		create_intf_ep_devs(iface);
	}
	return 0;
}
EXPORT_SYMBOL_GPL(usb_set_interface);

kernel/drivers/usb/core/usb.c

struct usb_interface *usb_ifnum_to_if(const struct usb_device *dev,
				      unsigned ifnum)
{
	struct usb_host_config *config = dev->actconfig;
	int i;
	if (!config)
	{
		return NULL;
	}
	
	for (i = 0; i < config->desc.bNumInterfaces; i++)
	{
		if (config->interface[i]->altsetting[0].desc.bInterfaceNumber == ifnum)
		{
			return config->interface[i];
		}
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(usb_ifnum_to_if);

kernel/include/linux/usb.h

struct usb_host_config {
	struct usb_config_descriptor	desc;

	char *string;		/* iConfiguration string, if present */

	/* List of any Interface Association Descriptors in this
	 * configuration. */
	struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];

	/* the interfaces associated with this configuration,
	 * stored in no particular order */
	struct usb_interface *interface[USB_MAXINTERFACES];

	/* Interface information available even when this is not the
	 * active configuration */
	struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];

	unsigned char *extra;   /* Extra descriptors */
	int extralen;
};

适当的在怀疑会出现报错的地方添加一些打印信息,我个人比较喜欢使用“printk()”,
然后经过反复测试,最终在usb_ifnum_to_if(const struct usb_device *dev,
unsigned ifnum)函数上添加以下语句。

struct usb_interface *usb_ifnum_to_if(const struct usb_device *dev,
				      unsigned ifnum)
{
	struct usb_host_config *config = dev->actconfig;
	int i;
	if (!config)
	{
		return NULL;
	}
	
	for (i = 0; i < config->desc.bNumInterfaces; i++)
	{
+		if (!config->interface[i]) 
+		{
+			printk("......................................................\n");
+			return NULL;		
+		}
		if (config->interface[i]->altsetting[0].desc.bInterfaceNumber == ifnum)
		{
			return config->interface[i];
		}
	}
	return NULL;
}

你可能感兴趣的:(瑞芯微RK驱动调试,linux,驱动开发,usb)