Linux: 记一次内核 UAF 问题解决过程

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文所涉软硬件环境:

SoC: 德州仪器(TI) AM335X,单核 1GHz,256MB DDR,32MB 存储 
内核:Linux 4.19

3. 问题的 重现 和 分析

在公司原有的产品上,增加 Gadget UAC(USB Audio Class) 功能,和其它相关硬件结合,构建一个 USB 声卡。其它相关硬件这里不做仔细介绍,因为它们和本文所述问题无关,也不影响对问题的理解。
Gadget UAC 功能编译成模块 g_audio.ko (使用 UAC 1.0),其它相关功能内置到内核,然后在 AM335X 上运行 insmod 指令加载模块:

# insmod g_audio.ko
[   49.480879] g_audio gadget: Linux USB Audio Gadget, version: Feb 2, 2012
[   49.491688] g_audio gadget: g_audio ready
[   49.765643] g_audio gadget: high-speed config #1: Linux USB Audio Gadget

上述日志表示模块加载成功,接下来查看 USB Gadget 声卡设备节点:

# ls /dev/snd
... pcmC1D0c pcmC1D0p ...

好的,USB Gadget 声卡设备节点 也生成了。这里之所以是 pcmC1* ,是因为系统中还有另一块声卡设备。接下来通过 USB 线将设备连接到 Windows ,从 Windows 的设备管理器,可以发现一个 用于播放的设备 和 一个 用于录音的设备,如下图:
在这里插入图片描述
接下来在 Windows 下启动 录音机程序,使用新连接的 USB 录音设备录音,在录音的过程中,在作为从设的 AM335X 上运行 Gadget UAC 模块卸载指令,然后再次重新加载模块,极高概率的会重现如下问题:

# rmmod g_audio
# insmod g_audio.ko
[  236.035428] Unable to handle kernel paging request at virtual address 06101835
[  236.042685] pgd = 3a0be3a7
[  236.045401] [06101835] *pgd=00000000
[  236.048997] Internal error: Oops: 5 [#1] PREEMPT ARM
[  236.053980] Modules linked in: g_audio [last unloaded: g_audio]
[  236.059936] CPU: 0 PID: 0 Comm: swapper Not tainted 4.19.94-g1194fe2-dirty #91
[  236.067185] Hardware name: Generic AM33XX (Flattened Device Tree)
[  236.073318] PC is at u_audio_iso_complete+0x30/0x334
[  236.078313] LR is at usb_gadget_giveback_request+0x14/0x18
[  236.083819] pc : [<c06dc1c8>]    lr : [<c06c82b8>]    psr: 60080193
[  236.090109] sp : c0e01b58  ip : c0e01b98  fp : c0e01b94
[  236.095352] r10: cf717600  r9 : d08fd410  r8 : cf738468
[  236.100597] r7 : cf738040  r6 : 00000001  r5 : 06101831  r4 : ca88c500
[  236.107149] r3 : 00000001  r2 : ca8a2e40  r1 : ca88c500  r0 : cf738468
[  236.113704] Flags: nZCv  IRQs off  FIQs on  Mode SVC_32  ISA ARM  Segment none
[  236.120954] Control: 10c5387d  Table: 8a868019  DAC: 00000051
[  236.126722] Process swapper (pid: 0, stack limit = 0xa080d62a)
[  236.132578] Stack: (0xc0e01b58 to 0xc0e02000)
[  236.136952] 1b40:                                                       c0e76ed8 00000000
[  236.145166] 1b60: 00000000 33200385 c0e01b8c ca88c500 cf738468 00000001 cf738040 c0e88408
[  236.153380] 1b80: d08fd410 cf717600 c0e01ba4 c0e01b98 c06c82b8 c06dc1a4 c0e01bcc c0e01ba8
[  236.161595] 1ba0: c06bc5e4 c06c82b0 c0e01bc8 dc2f03a6 cf738468 cf7384b8 cf738040 20080193
[  236.169809] 1bc0: c0e01bfc c0e01bd0 c06bce6c c06bc49c c06bcd10 cf738468 cf738468 ca87d180
[  236.178022] 1be0: 00000000 ca8ac100 ca87b7c8 ca8ac100 c0e01c14 c0e01c00 c06c8e7c c06bcd1c
[  236.186236] 1c00: 00000002 cf738468 c0e01c44 c0e01c18 c06dc628 c06c8e58 dc2f03a6 ca87d180
[  236.194451] 1c20: 00000002 cf7392b0 00000000 ca8ac000 ca8ac200 c0e03048 c0e01c6c c0e01c48
[  236.202665] 1c40: c06dcf80 c06dc59c 00000002 00000000 cf738040 c0e01d02 cf739278 00000002
[  236.210879] 1c60: c0e01cd4 c0e01c70 c06c364c c06dce8c 00000000 cf047600 00000009 c0e0ef80
[  236.219093] 1c80: cf047890 c0e149f8 c0e03104 c0e4ed8a 00000000 00000000 00000002 00000000
[  236.227306] 1ca0: 00000008 dc2f03a6 cf738040 cf738040 00000001 cf739040 00000000 d08fd410
[  236.235520] 1cc0: c0e8840c c0e03048 c0e01d3c c0e01cd8 c06ba290 c06c30a0 c0bf987c 00000002
[  236.243734] 1ce0: 00000000 00000036 c0e76ed8 00000000 d08fd400 d08fd400 cf738340 00000001
[  236.251948] 1d00: 0b016d80 00020000 00000000 dc2f03a6 00000000 cf739040 cf738040 00000000
[  236.260162] 1d20: c0e03048 00000000 c0e88404 cf73832c c0e01d9c c0e01d40 c06b8f68 c06b9bc0
[  236.268375] 1d40: 00000000 00000000 00000000 3b9aca00 ffffffe0 00000020 00012158 00000000
[  236.276589] 1d60: 00000000 00000099 00000000 dc2f03a6 00013880 00000000 cf738040 d0a44000
[  236.284803] 1d80: cf73832c cf577940 a0080193 00000001 c0e01dd4 c0e01da0 c06c0e40 c06b8954
[  236.293016] 1da0: c017e2d8 c0182fb4 60080113 cf721440 0000003d 00000000 c0e01e20 0000003d
[  236.301230] 1dc0: cf271300 c0e4ed77 c0e01e1c c0e01dd8 c016f774 c06c0cfc ffffe701 c0e77100
[  236.309445] 1de0: c0e77a40 c0bd17e0 c0bd17cc c0bd17a4 00000036 c0e03048 0000003d cf271300
[  236.317658] 1e00: 00000000 cf008000 c0e00000 c0e4ed60 c0e01e44 c0e01e20 c016f838 c016f6cc
[  236.325873] 1e20: 00000000 dc2f03a6 c017557c cf271300 0000003d 00000000 c0e01e5c c0e01e48
[  236.334087] 1e40: c016f8ec c016f810 cf271300 0000003d c0e01e74 c0e01e60 c01731f4 c016f894
[  236.342301] 1e60: c0e4d4cc 0000003d c0e01e84 c0e01e78 c016e868 c0173140 c0e01eac c0e01e88
[  236.350516] 1e80: c016f058 c016e848 c0e81bf4 60080013 ffffffff c0e01efc c0e43410 c0e00000
[  236.358730] 1ea0: c0e01ec4 c0e01eb0 c0455458 c016f008 c0108eb0 60080013 c0e01f24 c0e01ec8
[  236.366943] 1ec0: c0101a0c c0455428 00000000 000226d4 c0e143d0 c0118a80 00000000 c0e00000
[  236.375158] 1ee0: c0e030b4 00000000 c0e43410 c0e03048 c0e4ed60 c0e01f24 c0e01f28 c0e01f18
[  236.383372] 1f00: c0108eac c0108eb0 60080013 ffffffff 00000051 00000000 c0e01f34 c0e01f28
[  236.391586] 1f20: c096ef3c c0108e7c c0e01f74 c0e01f38 c0155158 c096ef20 ffffffff c0e03040
[  236.399800] 1f40: c0e50380 dc2f03a6 c0e01f6c c0e0e308 00000002 c0e03048 ffffffff c0e03040
[  236.408015] 1f60: c0e50380 c0d3ea30 c0e01f84 c0e01f78 c015550c c0155014 c0e01f9c c0e01f88
[  236.416229] 1f80: c0969484 c0155504 c0e503d8 c0e50380 c0e01ff4 c0e01fa0 c0d00dd0 c09693e0
[  236.424444] 1fa0: ffffffff ffffffff 00000000 c0d00708 00000000 cffff740 00000000 c0d3ea30
[  236.432658] 1fc0: dc2b10f8 00000000 00000000 c0d00330 00000051 10c03c7d 00000e05 8ef23000
[  236.440872] 1fe0: 413fc082 10c53c7d 00000000 c0e01ff8 00000000 c0d009d0 00000000 00000000
[  236.449079] Backtrace: 
[  236.451541] [<c06dc198>] (u_audio_iso_complete) from [<c06c82b8>] (usb_gadget_giveback_request+0x14/0x18)
[  236.461151]  r10:cf717600 r9:d08fd410 r8:c0e88408 r7:cf738040 r6:00000001 r5:cf738468
[  236.469011]  r4:ca88c500
[  236.471566] [<c06c82a4>] (usb_gadget_giveback_request) from [<c06bc5e4>] (musb_g_giveback+0x154/0x208)
[  236.480914] [<c06bc490>] (musb_g_giveback) from [<c06bce6c>] (musb_gadget_disable+0x15c/0x24c)
[  236.489563]  r7:20080193 r6:cf738040 r5:cf7384b8 r4:cf738468
[  236.495249] [<c06bcd10>] (musb_gadget_disable) from [<c06c8e7c>] (usb_ep_disable+0x30/0x3c)
[  236.503638]  r10:ca8ac100 r9:ca87b7c8 r8:ca8ac100 r7:00000000 r6:ca87d180 r5:cf738468
[  236.511499]  r4:cf738468 r3:c06bcd10
[  236.515090] [<c06c8e4c>] (usb_ep_disable) from [<c06dc628>] (u_audio_stop_playback+0x98/0xc4)
[  236.523649]  r5:cf738468 r4:00000002
[  236.527240] [<c06dc590>] (u_audio_stop_playback) from [<c06dcf80>] (f_audio_set_alt+0x100/0x220)
[  236.536064]  r10:c0e03048 r9:ca8ac200 r8:ca8ac000 r7:00000000 r6:cf7392b0 r5:00000002
[  236.543925]  r4:ca87d180 r3:dc2f03a6
[  236.547515] [<c06dce80>] (f_audio_set_alt) from [<c06c364c>] (composite_setup+0x5b8/0x1d24)
[  236.555901]  r7:00000002 r6:cf739278 r5:c0e01d02 r4:cf738040
[  236.561586] [<c06c3094>] (composite_setup) from [<c06ba290>] (musb_g_ep0_irq+0x6dc/0xda8)
[  236.569799]  r10:c0e03048 r9:c0e8840c r8:d08fd410 r7:00000000 r6:cf739040 r5:00000001
[  236.577659]  r4:cf738040
[  236.580203] [<c06b9bb4>] (musb_g_ep0_irq) from [<c06b8f68>] (musb_interrupt+0x620/0xcf0)
[  236.588329]  r10:cf73832c r9:c0e88404 r8:00000000 r7:c0e03048 r6:00000000 r5:cf738040
[  236.596189]  r4:cf739040
[  236.598733] [<c06b8948>] (musb_interrupt) from [<c06c0e40>] (dsps_interrupt+0x150/0x2b0)
[  236.606860]  r10:00000001 r9:a0080193 r8:cf577940 r7:cf73832c r6:d0a44000 r5:cf738040
[  236.614720]  r4:00000000
[  236.617270] [<c06c0cf0>] (dsps_interrupt) from [<c016f774>] (__handle_irq_event_percpu+0xb4/0x144)
[  236.626268]  r10:c0e4ed77 r9:cf271300 r8:0000003d r7:c0e01e20 r6:00000000 r5:0000003d
[  236.634127]  r4:cf721440
[  236.636671] [<c016f6c0>] (__handle_irq_event_percpu) from [<c016f838>] (handle_irq_event_percpu+0x34/0x84)
[  236.646368]  r10:c0e4ed60 r9:c0e00000 r8:cf008000 r7:00000000 r6:cf271300 r5:0000003d
[  236.654227]  r4:c0e03048
[  236.656771] [<c016f804>] (handle_irq_event_percpu) from [<c016f8ec>] (handle_irq_event+0x64/0x90)
[  236.665679]  r6:00000000 r5:0000003d r4:cf271300
[  236.670321] [<c016f888>] (handle_irq_event) from [<c01731f4>] (handle_level_irq+0xc0/0x160)
[  236.678705]  r5:0000003d r4:cf271300
[  236.682297] [<c0173134>] (handle_level_irq) from [<c016e868>] (generic_handle_irq+0x2c/0x3c)
[  236.690769]  r5:0000003d r4:c0e4d4cc
[  236.694359] [<c016e83c>] (generic_handle_irq) from [<c016f058>] (__handle_domain_irq+0x5c/0xb0)
[  236.703107] [<c016effc>] (__handle_domain_irq) from [<c0455458>] (omap_intc_handle_irq+0x3c/0x98)
[  236.712019]  r9:c0e00000 r8:c0e43410 r7:c0e01efc r6:ffffffff r5:60080013 r4:c0e81bf4
[  236.719800] [<c045541c>] (omap_intc_handle_irq) from [<c0101a0c>] (__irq_svc+0x6c/0xa8)
[  236.727834] Exception stack(0xc0e01ec8 to 0xc0e01f10)
[  236.732907] 1ec0:                   00000000 000226d4 c0e143d0 c0118a80 00000000 c0e00000
[  236.741121] 1ee0: c0e030b4 00000000 c0e43410 c0e03048 c0e4ed60 c0e01f24 c0e01f28 c0e01f18
[  236.749332] 1f00: c0108eac c0108eb0 60080013 ffffffff
[  236.754401]  r5:60080013 r4:c0108eb0
[  236.758011] [<c0108e70>] (arch_cpu_idle) from [<c096ef3c>] (default_idle_call+0x28/0x34)
[  236.766148] [<c096ef14>] (default_idle_call) from [<c0155158>] (do_idle+0x150/0x224)
[  236.773926] [<c0155008>] (do_idle) from [<c015550c>] (cpu_startup_entry+0x14/0x18)
[  236.781529]  r10:c0d3ea30 r9:c0e50380 r8:c0e03040 r7:ffffffff r6:c0e03048 r5:00000002
[  236.789388]  r4:c0e0e308
[  236.791935] [<c01554f8>] (cpu_startup_entry) from [<c0969484>] (rest_init+0xb0/0xb4)
[  236.799723] [<c09693d4>] (rest_init) from [<c0d00dd0>] (start_kernel+0x40c/0x434)
[  236.807236]  r5:c0e50380 r4:c0e503d8
[  236.810826] [<c0d009c4>] (start_kernel) from [<00000000>] (  (null))
[  236.817209] Code: e5925000 e373006c 13a03000 03a03001 (e5d52004) 
[  236.823336] ---[ end trace 285d6a3eef92cfac ]---
[  236.827972] Kernel panic - not syncing: Fatal exception in interrupt
[  236.834355] ---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---

有经验的童鞋,从日志内容:

[  236.035428] Unable to handle kernel paging request at virtual address 06101835
[  236.042685] pgd = 3a0be3a7
[  236.045401] [06101835] *pgd=00000000

立马可以判断出,这一个非法指针内存访问错误;另外,从日志内容:

[  236.073318] PC is at u_audio_iso_complete+0x30/0x334

可以判断出,该 非法指针内存访问错误 ,发生在函数 u_audio_iso_complete() 偏移 0x30 的位置。这里值得一提的是,像 u_audio_iso_complete+0x30/0x334 这种输出形式,u_audio_iso_complete() 表示函数名/ 前的 +0x30 表示相对函数起始位置的偏移/ 后的 0x334 表示整个函数长度字节数。数字的 0x 前缀自然是表示十六进制数字了。从这个信息,我们可以反汇编函数所在源码文件编译生成的目标文件 drivers/usb/gadget/function/u_audio.o,定位到出错位置指令。函数 u_audio_iso_complete() 的反汇编片段如下:

000003f4 <u_audio_iso_complete>:
 3f4:   e1a0c00d        mov     ip, sp
 3f8:   e92ddff0        push    {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
 3fc:   e24cb004        sub     fp, ip, #4
 400:   e24dd014        sub     sp, sp, #20
 404:   e1a04001        mov     r4, r1
 // struct uac_req *ur = req->context;
 408:   e5912020        ldr     r2, [r1, #32]
 40c:   e1a08000        mov     r8, r0
 // int status = req->status;
 410:   e591302c        ldr     r3, [r1, #44]   ; 0x2c
 // struct uac_rtd_params *prm = ur->pp;
 414:   e5925000        ldr     r5, [r2]
 418:   e373006c        cmn     r3, #108        ; 0x6c
 41c:   13a03000        movne   r3, #0
 420:   03a03001        moveq   r3, #1
 // prm->ep_enabled
 424:   e5d52004        ldrb    r2, [r5, #4] 
 428:   e1520003        cmp     r2, r3
 42c:   9a000019        bls     498 <u_audio_iso_complete+0xa4>
 430:   e5956008        ldr     r6, [r5, #8]
 434:   e5959000        ldr     r9, [r5]
 438:   e3560000        cmp     r6, #0

从前面的信息了解到,出错指令位于函数 u_audio_iso_complete() 偏移 +0x30 位置,也即上面反汇编代码 0x000003f4 + 0x30 = 0x424 的位置,这个位置的指令对应于下面代码中 prm->ep_enabled 访问:

/* drivers/usb/gadget/function/u_audio.c */

static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
{
	...
	int status = req->status;
	struct uac_req *ur = req->context;
	...
	struct uac_rtd_params *prm = ur->pp;
	....
	
	/* 
	 * 通过上面的反汇编分析,定位到正是此处 prm->ep_enabled 的访存操作导致内核崩溃。
	 * 原因在于 ur 所指向内存位置(也即 req->context),在 rmmod g_audio 期间已经释放
	 * 掉了(见后续分析),而持有该指针的 usb_request 对象没有释放,在重新 insmod g_audio 
	 * 期间,重新访问了 req->context 指向的无效内存地址,也即一次 UAF(Use After Free)错误。
	 */
	/* i/f shutting down */
	if (!prm->ep_enabled || req->status == -ESHUTDOWN)
		return;
	
	...
}

接下来,我们来仔细分析这整个过程。先来点对 Gadget UAC 驱动 的前置了解,整个 Gadget UAC 驱动 的加载过程,正如所有的 USB 设备一样,首先经过设备枚举过程,然后是各驱动特定的数据包发送。此时 AM335X 所在的 USB 作为从设,而 Windows(或其主机操作系统,如 Ubuntu, MAC OS 等) 作为 主机 一侧。作为从设一侧的 Gadget 驱动(包括 Gadget UAC) 通过 UDC(USB Device Controller) 接收数据包,每个数据包都会在 UDC 内生成一个中断,UDC 的中断处理接口,会将这些数据传递给 Gadget 驱动 处理,这些数据是以 usb_request 数据结构进行封装的,对数据处理的最后会调用一个预先设定的回调,本文涉及的回调是 u_audio_iso_complete()
Gadget UAC 驱动 有两个 USB 端点(EP: EndPoint) 提供的用于 播放录音 功能(interface/function)。当 Windows 开始录音之初,会分配用于 USB 录音数据处理的 usb_request ,来看一下细节:

/* drivers/usb/gadget/function/f_uac1.c */
static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
	...

	if (intf == uac1->as_out_intf) {
		...
	}  else if (intf == uac1->as_in_intf) { /* 启用 或 关闭 录音 inerface/function/EP */
		uac1->as_in_alt = alt;
		
		if (alt) // Windows 开始录音
			ret = u_audio_start_playback(&uac1->g_audio);
		else // Windows 停止录音
			u_audio_stop_playback(&uac1->g_audio);
	}

	return ret;
}

/* drivers/usb/gadget/function/u_audio.c */
int u_audio_start_playback(struct g_audio *audio_dev)
{
	struct usb_request *req;
	
	...

	prm->ep_enabled = true;
	usb_ep_enable(ep); // 激活录音用 EndPoint

	// 分配录音数据处理的 usb_request
	for (i = 0; i < params->req_number; i++) {
		req = usb_ep_alloc_request(ep, GFP_ATOMIC);

		prm->ureq[i].req = req;
		prm->ureq[i].pp = prm;
		
		...

		req->complete = u_audio_iso_complete; // 设置数据处理回调
		req->buf = prm->rbuf + i * prm->max_psize;

		// 将新分配的 usb_request 加入到录音端点的 数据包处理对象 队列,用于后续录音数据包处理。
		if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
			...
	}

	return 0;
}

// 将新分配的 usb_request 加入到录音端点的 数据包处理对象 队列,用于后续录音数据包处理。
usb_ep_queue()
	ep->ops->queue(ep, req, gfp_flags) = musb_gadget_queue()
		...
		/* add request to the list */
		list_add_tail(&request->list, &musb_ep->req_list); // usb_requet 对象【入队】
		...

上面对原始代码做了细微调整,以突出我们分析关注的重点。接下来看数据的处理流程:

dsps_interrupt() // UDC 中断处理
	musb_interrupt()
		...
		musb_g_giveback()
			...
			list_del(&req->list); // usb_requet 对象【出队】
			...
			usb_gadget_giveback_request()
				req->complete(ep, req) = u_audio_iso_complete()

/* drivers/usb/gadget/function/u_audio.c */
static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
{
	...
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		// 录音数据处理
	}  else {
	}
	...
exit:
	// usb_requet 对象【重新入队】,用于下一次数据处理
	if (usb_ep_queue(ep, req, GFP_ATOMIC))
		dev_err(uac->card->dev, "%d Error!\n", __LINE__);	
}

对上面的流程简单总结下:

1. Windows 启动录音,于是 Gadget UAC 驱动对应的响应是:
   分配 2(默认配置) usb_request ,然后加入用于录音端点的请求队列。
2. 录音过程中,UDC 生成中断,将主机的请求数据包通过 usb_request 传递给 Gadget UAC 驱动
   处理;usb_request 包含的数据在处理期间,usb_request 将从 录音端点的队列中移出,处于
   出队状态,等到在 u_audio_iso_complete() 中将数据处理完成,又重新将 usb_request 放置
   到录音端点的队列中。

我们的问题在于:在录音期间,在录音端点的 usb_request 队列不为空时,将驱动模块 g_audio.ko 卸载了,导致下次重新加载 g_audio.ko 模块时,使用上次加载区间的 usb_request 请求数据。因为 usb_request 对象本身在模块卸载期间没有释放,所以 usb_request 对象本身没有问题,问题在于 usb_request 包含的指针指向的内存被释放掉了,再拿这些指针访问数据,自然就造成了问题(UAF: Use After Free)
看一下 g_audio.ko 模块卸载时的相关细节:

/* drivers/usb/gadget/function/f_uac1.c */
static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
{
	...
	g_audio_cleanup(audio);
	...
}

/* drivers/usb/gadget/function/u_audio.c */
void g_audio_cleanup(struct g_audio *g_audio)
{
	...
	// Oops!!! usb_request::context 指向的内存释放掉了
	kfree(uac->p_prm.ureq);
	kfree(uac->c_prm.ureq);
	// Oops!!! usb_request::buf 指向的内存释放掉了
	kfree(uac->p_prm.rbuf);
	kfree(uac->c_prm.rbuf);
	...
}

这里对内存资源的释放,就是我们问题的根因了。修正的方法自然不是注释掉这里的内存释放代码:这里清理操作是正确的,毕竟模块都卸载了。修正的方式也很简单,就是在模块卸载的时候,将录音端点上请求队列中残留的 usb_request 移除并释放掉:

/* drivers/usb/gadget/function/f_uac1.c */

static void f_audio_disable(struct usb_function *f)
{
	struct f_uac1 *uac1 = func_to_uac1(f);

	uac1->as_out_alt = 0;
	uac1->as_in_alt = 0;

+   u_audio_stop_playback();
	u_audio_stop_capture(&uac1->g_audio);
}

进一步看一下 u_audio_stop_playback() 是如何清理掉的 usb_request 的:

void u_audio_stop_playback(struct g_audio *audio_dev)
{
	struct snd_uac_chip *uac = audio_dev->uac;

	free_ep(&uac->p_prm, audio_dev->in_ep);
}

static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep)
{
	struct snd_uac_chip *uac = prm->uac;
	struct g_audio *audio_dev;
	struct uac_params *params;
	int i;

	if (!prm->ep_enabled)
		return;

	prm->ep_enabled = false;

	audio_dev = uac->audio_dev;
	params = &audio_dev->params;

	for (i = 0; i < params->req_number; i++) {
		if (prm->ureq[i].req) {
			usb_ep_dequeue(ep, prm->ureq[i].req);
			usb_ep_free_request(ep, prm->ureq[i].req);
			prm->ureq[i].req = NULL;
		}
	}

	if (usb_ep_disable(ep))
		dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
}

usb_ep_disable()
	ep->ops->disable(ep) = musb_gadget_disable()
		nuke(musb_ep, -ESHUTDOWN) // ESHUTDOWN 指示不再处理
			while (!list_empty(&ep->req_list)) {
				req = list_first_entry(&ep->req_list, struct musb_request, list);
				musb_g_giveback(ep, &req->request, status);
					...
					list_del(&req->list); // 移出 usb_request
					...
					// 移出后处理
					usb_gadget_giveback_request(&req->ep->end_point, &req->request)
						req->complete(ep, req) = u_audio_iso_complete()
							// ESHUTDOWN 指示不再处理,usb_request 也不再重新入队
							if (!prm->ep_enabled || req->status == -ESHUTDOWN)
								return;
							...
					...
			}

4. 分析手段

各位看官,估计你们也看到了,这个古老的 AM335X 资源非常有限,什么 CONFIG_DEBUG_INFO ,什么 slub debug (受限于资源用不起来,也对问题没有帮助),更不用提什么 crash dump 之类的手段了,那就只剩下最基础原始的 printk() 。没错,就是通过 printk() 打印对应对象的指针地址,再结合前面的反汇编,以及源码的阅读,最终定位并解决了问题。

5. 后记

笔者个人觉得,不管什么调试手段,始终代替不了对源代码的熟悉。为什么这么说,当笔者解决问题后,在前段时间同步的内核社区稳定分支代码(stable),发现没有修正该问题,所以准备提交补丁到社区,结果同步下来最新的 linux-next 分之后,查看代码,发现 f_uac1.c 驱动的原作者,已经于 2021 年修正了该问题。而作者的修复,他说的比较含混,似乎没有触发本文除问题的场景,而是从代码的角度审视,认为模块卸载时的清理工作不完整,所以添加了相应逻辑,详见此处:
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/drivers/usb/gadget/function/f_uac1.c?id=cc2ac63d4cf72104e0e7f58bb846121f0f51bb19
这种修正就是源于对代码的极度熟悉了解。如果对代码熟悉,哪里用得着长达 3 天苦逼的 printk() 啊~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值