DF-5348 / fix.diff
diff --git a/sys/platform/pc64/x86_64/busdma_machdep.c b/sys/platform/pc64/x86_64/busdma_machdep.c --- a/sys/platform/pc64/x86_64/busdma_machdep.c +++ b/sys/platform/pc64/x86_64/busdma_machdep.c @@ -442,6 +442,15 @@ if (map != NULL && map != (void *)-1) { if (STAILQ_FIRST(&map->bpages) != NULL) return (EBUSY); + /* + * A map with a pending deferred load (queued on the + * bounce zone waiting list or on the global callback + * list) always has pagesneeded != 0. Freeing it here + * would leave dangling references on those lists + * (use-after-free). + */ + if (map->pagesneeded != 0 || map->pagesreserved != 0) + return (EBUSY); kfree(map, M_DEVBUF); } dmat->map_count--; @@ -1023,11 +1032,29 @@ _bus_dmamap_unload(bus_dma_tag_t dmat, bus_dmamap_t map) { struct bounce_page *bpage; + struct bounce_zone *bz; while ((bpage = STAILQ_FIRST(&map->bpages)) != NULL) { STAILQ_REMOVE_HEAD(&map->bpages, links); free_bounce_page(dmat, bpage); } + + /* + * If the map is queued for a deferred load, dequeue it and + * release its bounce page reservation. Otherwise the deferred + * load would later run with stale buf/buflen/callback state + * after the driver has already aborted the transaction. + */ + if (map->pagesneeded != 0 || map->pagesreserved != 0) { + bz = dmat->bounce_zone; + if (bz != NULL) { + BZ_LOCK(bz); + STAILQ_REMOVE(&bz->bounce_map_waitinglist, map, + bus_dmamap, links); + BZ_UNLOCK(bz); + } + return_bounce_pages(dmat, map); + } } void @@ -1308,7 +1335,7 @@ BZ_UNLOCK(bz); if (wait_map != NULL) - add_map_callback(map); + add_map_callback(wait_map); } static bus_addr_t @@ -1442,6 +1469,17 @@ while ((map = STAILQ_FIRST(&bounce_map_callbacklist)) != NULL) { STAILQ_REMOVE_HEAD(&bounce_map_callbacklist, links); spin_unlock(&bounce_map_list_spin); + /* + * Skip maps that were unloaded while queued: their + * pagesneeded/pagesreservation were reset and the + * driver considers the transaction dead. + */ + if (map->pagesneeded == 0 && + STAILQ_FIRST(&map->bpages) == NULL) { + kprintf("busdma: dropping callback for unloaded map\n"); + spin_lock(&bounce_map_list_spin); + continue; + } bus_dmamap_load(map->dmat, map, map->buf, map->buflen, map->callback, map->callback_arg, /*flags*/0); spin_lock(&bounce_map_list_spin); |