Fix busdma resource leak on usb device detach.

When a usb device is detached, usb_pc_dmamap_destroy() called
bus_dmamap_destroy() while the map was still loaded. That's harmless on x86
architectures, but on all other platforms it causes bus_dmamap_destroy() to
return EBUSY and leak away any memory resources (including bounce buffers)
associated with the mapping, as well as any allocated map structure itself.

This change introduces a new is_loaded flag to the usb_page_cache struct to
track whether a map is loaded or not. If the map is loaded,
bus_dmamap_unload() is called before bus_dmamap_destroy() to avoid leaking
away resources.

Differential Revision:	https://reviews.freebsd.org/D32208

(cherry picked from commit dc91a9715f)
This commit is contained in:
Ian Lepore 2021-09-28 13:29:10 -06:00
parent 4747ec0839
commit b1cca74367
2 changed files with 14 additions and 3 deletions

View file

@ -600,6 +600,7 @@ usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg,
bus_dmamem_free(utag->tag, ptr, map);
goto error;
}
pc->isloaded = 1;
memset(ptr, 0, size);
usb_pc_cpu_flush(pc);
@ -612,6 +613,7 @@ error:
pc->page_start = NULL;
pc->page_offset_buf = 0;
pc->page_offset_end = 0;
pc->isloaded = 0;
pc->map = NULL;
pc->tag = NULL;
return (1);
@ -626,11 +628,13 @@ void
usb_pc_free_mem(struct usb_page_cache *pc)
{
if (pc && pc->buffer) {
bus_dmamap_unload(pc->tag, pc->map);
if (pc->isloaded)
bus_dmamap_unload(pc->tag, pc->map);
bus_dmamem_free(pc->tag, pc->buffer, pc->map);
pc->buffer = NULL;
pc->isloaded = 0;
}
}
@ -662,7 +666,8 @@ usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync)
* We have to unload the previous loaded DMA
* pages before trying to load a new one!
*/
bus_dmamap_unload(pc->tag, pc->map);
if (pc->isloaded)
bus_dmamap_unload(pc->tag, pc->map);
/*
* Try to load memory into DMA.
@ -675,6 +680,7 @@ usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync)
err = 0;
}
if (err || uptag->dma_error) {
pc->isloaded = 0;
return (1);
}
} else {
@ -682,7 +688,8 @@ usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync)
* We have to unload the previous loaded DMA
* pages before trying to load a new one!
*/
bus_dmamap_unload(pc->tag, pc->map);
if (pc->isloaded)
bus_dmamap_unload(pc->tag, pc->map);
/*
* Try to load memory into DMA. The callback
@ -693,6 +700,7 @@ usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync)
&usb_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) {
}
}
pc->isloaded = 1;
} else {
if (!sync) {
/*
@ -785,6 +793,8 @@ void
usb_pc_dmamap_destroy(struct usb_page_cache *pc)
{
if (pc && pc->tag) {
if (pc->isloaded)
bus_dmamap_unload(pc->tag, pc->map);
bus_dmamap_destroy(pc->tag, pc->map);
pc->tag = NULL;
pc->map = NULL;

View file

@ -101,6 +101,7 @@ struct usb_page_cache {
* from the memory. Else write. */
uint8_t ismultiseg:1; /* set if we can have multiple
* segments */
uint8_t isloaded:1; /* Set if map is currently loaded. */
#endif
};