diff --git a/avf/shim/avfboot.c b/avf/shim/avfboot.c index 87b3341ad..8e76d7e23 100644 --- a/avf/shim/avfboot.c +++ b/avf/shim/avfboot.c @@ -718,6 +718,17 @@ void avfboot_main(uint64_t fdt_pa) uart_kv("+", scgpu_size); uart_putc('\n'); } + /* Scion's speaker (SCSND, xnu/files/bsd/sys/hakios.scsnd.h): a ring of samples, likewise */ + uint64_t scsnd_pa = arg_num(m.bootargs, "scsnd.addr="), scsnd_size = arg_num(m.bootargs, "scsnd.size="); + if (scsnd_pa || scsnd_size) { + if (scsnd_pa < m.ram_lo || scsnd_pa + scsnd_size > m.ram_hi || (scsnd_pa & 0xfff) || scsnd_size < 0x10000) + die("scsnd.addr/size do not describe a region in RAM"); + if (scsnd_pa < win_end) + win_end = scsnd_pa & ~(BASE_ALIGN - 1); + uart_kv("avfboot: scsnd ", scsnd_pa); + uart_kv("+", scsnd_size); + uart_putc('\n'); + } if (win_end <= phys_base) die("not enough RAM"); uint64_t win_size = win_end - phys_base; diff --git a/xnu/files/bsd/dev/hakios.scsnd.c b/xnu/files/bsd/dev/hakios.scsnd.c new file mode 100644 index 000000000..52cb94061 --- /dev/null +++ b/xnu/files/bsd/dev/hakios.scsnd.c @@ -0,0 +1,92 @@ +/* + * SCSND: the sink of /dev/hakios-audio under AVF, the Scion app (sys/hakios.scsnd.h, patch 1401). + * + * With scsnd.addr= and scsnd.size= the app has lent a region with a header and a ring of samples. + * What the audio server writes goes into the ring as it comes, and the app plays it through Android's + * audio. Never waits (the writer is the audio server's real-time thread): what does not fit is lost, + * which only happens when the app has stopped taking. iOS's volume is passed on in the header. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern vm_offset_t ml_io_map_unmappable(vm_offset_t phys_addr, vm_size_t size, unsigned int flags); + +static volatile struct scsnd_header *scsnd; +static uint8_t *scsnd_ring; +static uint32_t scsnd_bytes; +static uint32_t volume_seen = ~0u; + +/* Called with /dev/hakios-audio's lock held: one writer at a time. */ +static uint32_t +scsnd_play(const int16_t *frames, uint32_t count) +{ + volatile struct scsnd_header *h = scsnd; + uint32_t below, generation; + bool muted; + + if (hakios_audio_volume(&below, &muted, &generation) && generation != volume_seen) { + h->below = below; + h->muted = muted ? 1 : 0; + volume_seen = generation; + } + uint32_t head = h->head; /* (ours alone) */ + uint32_t tail = __atomic_load_n((uint32_t *)(uintptr_t)&h->tail, __ATOMIC_ACQUIRE); + uint32_t used = head - tail, room = used <= scsnd_bytes ? scsnd_bytes - used : 0; + uint32_t bytes = count * HAKIOS_AUDIO_FRAME; + + if (bytes > room) { + bytes = room - room % HAKIOS_AUDIO_FRAME; + } + uint32_t at = head & (scsnd_bytes - 1); + uint32_t first = bytes < scsnd_bytes - at ? bytes : scsnd_bytes - at; + memcpy(scsnd_ring + at, frames, first); + memcpy(scsnd_ring, (const uint8_t *)frames + first, bytes - first); + __atomic_store_n((uint32_t *)(uintptr_t)&h->head, head + bytes, __ATOMIC_RELEASE); + if (bytes < count * HAKIOS_AUDIO_FRAME) { + h->dropped += count - bytes / HAKIOS_AUDIO_FRAME; + } + return bytes / HAKIOS_AUDIO_FRAME; +} + +void +hakios_scsnd_init(void) +{ + uint64_t pa = 0, size = 0; + volatile struct scsnd_header *h; + + if (!PE_parse_boot_argn("scsnd.addr", &pa, sizeof(pa)) || pa == 0) { + return; + } + (void)PE_parse_boot_argn("scsnd.size", &size, sizeof(size)); + if ((pa & PAGE_MASK) || size < 0x10000 || size > (64ull << 20)) { + printf("scsnd: scsnd.addr=%#llx scsnd.size=%#llx: not a region\n", pa, size); + return; + } + h = (volatile struct scsnd_header *)ml_io_map_unmappable((vm_offset_t)pa, (vm_size_t)round_page(size), VM_WIMG_DEFAULT); + if (h == NULL || h->magic != SCSND_MAGIC || h->version != SCSND_VERSION) { + printf("scsnd: no header from the app at %#llx (magic %#x): no sound\n", pa, h != NULL ? h->magic : 0); + return; + } + uint32_t off = h->ring_off, bytes = h->ring_bytes; + if (off < sizeof(struct scsnd_header) || bytes < 4096 || (bytes & (bytes - 1)) || (uint64_t)off + bytes > size || + h->rate != HAKIOS_AUDIO_RATE || h->channels != HAKIOS_AUDIO_CHANNELS) { + printf("scsnd: the app's ring (%u bytes at %u, %u Hz, %u channels) is not one to write into\n", bytes, off, + h->rate, h->channels); + return; + } + scsnd = h; + scsnd_ring = (uint8_t *)(uintptr_t)h + off; + scsnd_bytes = bytes; + __atomic_store_n((uint32_t *)(uintptr_t)&h->guest_ready, 1, __ATOMIC_RELEASE); + printf("scsnd: the app's speaker at %#llx: a ring of %u ms\n", pa, + bytes / HAKIOS_AUDIO_FRAME * 1000 / HAKIOS_AUDIO_RATE); + hakios_audio_set_sink("the Scion app (SCSND)", scsnd_play); +} diff --git a/xnu/files/bsd/sys/hakios.scsnd.h b/xnu/files/bsd/sys/hakios.scsnd.h new file mode 100644 index 000000000..e9bc383f6 --- /dev/null +++ b/xnu/files/bsd/sys/hakios.scsnd.h @@ -0,0 +1,44 @@ +/* + * SCSND: iOS's sound played by the Scion app (patch 1401, for the Android Virtualization Framework). + * + * AVF's own sound device (crosvm's vhost-user virtio-snd) failed to start two times in three on the + * Pixel 10, and the VM went with it. Instead the app lends the guest a region of RAM, as it lends + * the framebuffer's (SCFB), the camera's (SCCAM) and the GPU's (SCGPU), and says where with + * scsnd.addr= scsnd.size= in the kernel parameters; avfboot keeps it out of the kernel's memory. + * The region begins with the header below, which the app writes before the VM starts, and holds a + * ring of samples in /dev/hakios-audio's one format (sys/hakios.audio.h: 48000 frames a second, + * 16-bit, left then right). The kernel's sink (bsd/dev/hakios.scsnd.c) puts in what the audio server + * writes; the app takes it out and plays it through Android's audio (AAudio). + * + * The ring: ring_bytes (a power of two, whole frames) at ring_off from the region's start. head and + * tail count bytes, free-running, modulo 2^32: the guest alone moves head (after the samples are in, + * with a barrier), the app alone moves tail (after it has taken them). What is in: head - tail. The + * guest never waits: what does not fit is lost (counted in dropped). The app never lets more than a + * little build up: if it finds too much, it skips ahead (counted in skipped). + * + * The volume iOS sets on its speaker (HAKIOS_AUDIO_SET_VOLUME) is passed on in below/muted, and the + * app scales by it: iOS's volume is then a part of Android's media volume. + */ +#ifndef _SYS_HAKIOS_SCSND_H_ +#define _SYS_HAKIOS_SCSND_H_ + +#include + +#define SCSND_MAGIC 0x4e534353u /* "SCSN" */ +#define SCSND_VERSION 1u + +struct scsnd_header { + uint32_t magic, version; /* the app's */ + uint32_t ring_off, ring_bytes; /* the app's: where the ring is, and how big */ + uint32_t rate, channels; /* the app's: 48000, 2 (what it plays) */ + uint32_t head; /* the guest's: bytes put in */ + uint32_t tail; /* the app's: bytes taken out */ + uint32_t below, muted; /* the guest's: iOS's volume, eighths of a dB under the loudest; muted */ + uint32_t guest_ready; /* the guest's: 1 once its sink is in place */ + uint32_t dropped; /* the guest's: frames that did not fit */ + uint32_t skipped; /* the app's: frames it skipped to catch up */ + uint32_t underruns; /* the app's: times it had less than it was asked for */ + uint32_t reserved[18]; /* (128 bytes in all) */ +}; + +#endif diff --git a/xnu/patches/1401-avf-scion-sound.patch b/xnu/patches/1401-avf-scion-sound.patch new file mode 100644 index 000000000..3aab6790e --- /dev/null +++ b/xnu/patches/1401-avf-scion-sound.patch @@ -0,0 +1,27 @@ +diff --git a/bsd/conf/files b/bsd/conf/files +index dcc51a5..48d9d32 100644 +--- a/bsd/conf/files ++++ b/bsd/conf/files +@@ -145,6 +145,7 @@ bsd/dev/hakios.apfs_check.c optional apfs + bsd/dev/hakios.sound.c standard + bsd/dev/hakios.vgpu3d.c standard + bsd/dev/hakios.vgpu3d_xnu.c standard ++bsd/dev/hakios.scsnd.c standard + bsd/dev/hakios.qmi.c standard + bsd/dev/hakios.nas.c standard + bsd/dev/hakios.wlfw.c standard +diff --git a/bsd/dev/hakios.audio.c b/bsd/dev/hakios.audio.c +index de97d92..77b49c4 100644 +--- a/bsd/dev/hakios.audio.c ++++ b/bsd/dev/hakios.audio.c +@@ -347,6 +347,10 @@ hakios_audio_init(void) + report_call = thread_call_allocate(audio_report, NULL); + audio_recording_init(); + hakios_virtio_sound_init(); ++ { ++ extern void hakios_scsnd_init(void); ++ hakios_scsnd_init(); /* scsnd.addr=: the Scion app plays it (bsd/dev/hakios.scsnd.c) */ ++ } + hakios_speaker_init(); + hakios_haptic_init(); + hakios_torch_init();