From 786fdabc7d02dd795bc391c31ba3fb95f469b0de Mon Sep 17 00:00:00 2001 From: Makr Date: Thu, 22 Oct 2026 20:10:41 +0100 Subject: [PATCH 1/2] aop: pass handlers the reply length without the result word (port from MakrSas/Inferno) An AOP endpoint reply holds the 4-byte result word followed by out_len bytes of payload. handle_command was given actual_out_payload, which already points past the result word, together with actual_out_len, which still counts the word. A handler that filled the buffer it was told about wrote 4 bytes past the end of the allocation, into the next heap block. MakrSas saw malloc later spin in the damaged free list with the guest stopped. Handlers are now given out_len. Ported from MakrSas/Inferno commit 181c83d1d5 ("aop: stop writing past the end of the reply buffer", branch ios) by Makr , under the same licence as the file. Co-Authored-By: Claude Opus 5.5 --- hw/misc/aop.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hw/misc/aop.c b/hw/misc/aop.c index a8b7ac17bc..ebb924128e 100644 --- a/hw/misc/aop.c +++ b/hw/misc/aop.c @@ -556,8 +556,12 @@ static void apple_aop_ep_handle_message(void* opaque, uint8_t ep, uint64_t msg) g_free(actual_out_payload); } else { + // Past the result word there are out_len bytes; actual_out_len counts the word as well. + // Given that size, a handler that fills its buffer writes four bytes past the end of the + // allocation, into the next block on the heap, and malloc later spins in the damaged + // freelist with the guest stopped around it. res = s->descr->handle_command(s->opaque, seq, actual_in_payload, actual_in_len, - actual_out_payload, actual_out_len); + actual_out_payload, out_len); } stl_le_p(out_payload, res); From 09fb289981635015e1ce91b257573b99a8d3f217 Mon Sep 17 00:00:00 2001 From: Makr Date: Thu, 22 Oct 2026 20:10:41 +0100 Subject: [PATCH 2/2] sio: complete every transfer a stop leaves queued (port from MakrSas/Inferno) A SIO DMA stop destroyed the buffers still queued on the channel and sent a bare completion. XNU's AppleSmartIODMA::_stopDMA sleeps, uninterruptibly, until the channel goes idle, and a channel goes idle only once every command it handed out has come back. A stream normally ends with a buffer or two still queued, so the stopping thread never woke. With audio, MakrSas saw this leave mediaserverd in TH_WAIT|TH_UNINT after the first stop; the watchdog then killed it and all sound was gone until reboot. Every buffer still out is now written back as completed, with its tag, the bytes done so far and its timestamps, ahead of the stop's own completion. Ported from MakrSas/Inferno commit 2a79c1923b ("sio: hand back every transfer a stop leaves queued", branch ios) by Makr , under the same licence as the file. Co-Authored-By: Claude Opus 5.5 --- hw/dma/apple_sio.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hw/dma/apple_sio.c b/hw/dma/apple_sio.c index 6d5883c679..4c234f15c2 100644 --- a/hw/dma/apple_sio.c +++ b/hw/dma/apple_sio.c @@ -214,6 +214,8 @@ static void apple_sio_dma_del_buffers(AppleSIODMAEndpoint* ep) QTAILQ_FOREACH_SAFE (buf, &ep->buffers, next, buf_next) { apple_sio_dma_destroy_buffer(ep, buf); } } +static void apple_sio_dma_writeback(AppleSIO* s, AppleSIODMAEndpoint* ep, SIODMABuffer* buf); + // -- internal references -- // Firestorm$Inferno/18A5351d/sio.bndb@000030e4{sio_endpoint::handle_message}+0x64 // -- end internal references -- @@ -224,7 +226,17 @@ static void apple_sio_dma_stop(AppleSIO* s, AppleSIODMAEndpoint* ep) SIOMessage m = {0}; AppleRTKit* rtk = &s->parent_obj; - QTAILQ_FOREACH_SAFE (buf, &ep->buffers, next, buf_next) { apple_sio_dma_destroy_buffer(ep, buf); } + /* + * Every transfer still out comes back completed, however little of it was done. The kernel's stop + * waits, uninterruptibly, for the channel to go idle, and a channel goes idle only once every command + * it handed out has come back. Dropped quietly, they left the audio server's IO thread asleep in + * AppleSmartIODMA::_stopDMA for good — the first time right after the actuator's first vibration — + * and every sound in the system went with it. + */ + QTAILQ_FOREACH_SAFE (buf, &ep->buffers, next, buf_next) { + if (!buf->mapped) { buf->start_timestamp = apple_sio_get_cur_ts(s); } + apple_sio_dma_writeback(s, ep, buf); + } m.op = OP_COMPLETE; m.ep = ep->id;