Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 66 additions & 34 deletions hw/arm/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,35 +42,9 @@

static const char* KEEP_COMP[] = {
"adbe0,s8000\0$",
// "aop-audio\0$",
// "aop-audio-control\0$",
// "aop-audio-speaker\0$",
// "audio-aop-lp-mic-in\0$",
// "audio-aop-mca2\0$",
// "audio-aop-pcmaudiomgr\0$",
// "alc,t8030\0$",
// "audio-data,aop-audio-haptic\0$",
// "audio-data,halogen\0$",
// "audio-data,hawking\0$",
// "audio-data,baseband-voice\0$",
// "audio-data,dsp-debug1\0$",
// "audio-data,mikeybus-secondary\0$",
// "audio-data,aop-audio-hpdb\0$",
// "halle-sensor,aop-ad5860-config\0$",
// "audio-aop-hall\0$",
// "audio-aop-hpdbg\0$",
// "audio-aop-haptic\0$",
// "audio-aop-haptic-leap\0$",
// "audio-aop-hapticmgr\0$",
// "aop-audio-hpdbg\0$",
// "audio-data,aop-audio-hpdbg\0$",
// "haptics-support,leap\0$",
// "audio,embedded-resource-manager\0$",
// "audio-control,cs35l27\0$",
// "audio-data,audio-loopback\0$",
// "audio-data,cs35l27\0$",
// "audio-data,cs42l77\0$",
// "audio-control,cs42l77\0$",
"aes,s8000\0$",
"aic,1\0$",
"apcie-bridge\0$",
Expand Down Expand Up @@ -108,9 +82,6 @@ static const char* KEEP_COMP[] = {
"iop,s8000\0$",
"iop-nub,sep\0$",
"lcd,pinot\0$",
// "mca-switch,t8030\0$",
// "mcaCluster,t8030\0$",
// "mca,t8030\0$",
"mipi-dsim-1,synopsys\0$",
"N104AP\0iPhone12,1\0AppleARM\0$",
"N104DEV\0iPhone12,1\0AppleARM\0$",
Expand Down Expand Up @@ -174,6 +145,64 @@ static const char* KEEP_COMP[] = {
"i2c,t8101\0i2c,s5l8940x\0iic,soft\0$",
};

// Kept only with audio=on (AppleBootInfo.audio). Each of these nodes pulls in a
// driver that waits on its neighbours, so the tree is only useful complete; without
// audio=on none of it reaches the guest and none of those drivers load.
// The audio part of this list follows MakrSas/Inferno (branch ios), which got
// playback and recording working end to end with it.
static const char* AUDIO_COMP[] = {
// The speaker path: the MCA (I2S) switch and clusters, the CS42L77 codec on
// SPI3, the CS35L27 amp on I2C2 and the audio resource manager.
"mca-switch,t8030\0$",
"mcaCluster,t8030\0$",
"mca,t8030\0$",
"audio-control,cs35l27\0$",
"audio-data,cs35l27\0$",
"audio-control,cs42l77\0$",
"audio-data,cs42l77\0$",
"audio,embedded-resource-manager\0$",
// The AOP audio service (hw/audio/aop-audio.c) and its devices. The amp waits
// for aop-audio-speaker as its power provider, and the resource manager needs
// the PCM asset manager before it publishes anything to userspace.
"aop-audio\0$",
"aop-audio-speaker\0$",
"aop-audio-control\0$",
"audio-aop-pcmaudiomgr\0$",
// The AOP's own MCA data devices: the model advertises 'mca0' and 'mca1'.
"audio-aop-mca2\0$",
// The always-listening microphone ('lpai'). The CS42L77's function-codec_lpmic
// names it; without it the AOP audio controller cannot hand out buffers.
"audio-aop-lp-mic-in\0$",
// The CS42L77's function-halogen_master, -hawk_master, -dsp-debug1_master and
// -msp_master name these data nodes; without them AppleCS42L77Audio::start
// waits for providers that never appear.
"audio-data,halogen\0$",
"audio-data,hawking\0$",
"audio-data,dsp-debug1\0$",
"audio-data,mikeybus-secondary\0$",
"audio-data,audio-loopback\0$",
// alc0..3: the I2S links the AOP drives itself ('aph ', 'aphd'), with their
// headphone-debug children.
"alc,t8030\0$",
"aop-audio-hpdbg\0$",
"audio-data,aop-audio-hpdbg\0$",
// The haptics chain. The audio server counts the actuator as a transducer;
// without it the built-in route comes up empty (AVAudioSession 'nort').
"aop-audio-haptic\0$",
"audio-aop-hapticmgr\0$",
"audio-data,aop-audio-haptic\0$",
"audio-aop-haptic-leap\0$",
"haptics-support,leap\0$",
"audio-aop-hall\0$",
"halle-sensor,aop-ad5860-config\0$",
};

// Names in REM_NAMES that audio=on keeps: `aop-mca` is the AOP's own MCA, which
// the AOP audio devices describe their links through.
static const char* AUDIO_KEEP_NAMES[] = {
"aop-mca\0$",
};

static const char* REM_NAMES[] = {
"accel\0$",
"gyro\0$",
Expand Down Expand Up @@ -292,7 +321,7 @@ static bool apple_boot_dt_match(const AppleDTProp* prop, const char** list, size
return false;
}

static void apple_boot_process_dt_node(AppleDTNode* node, AppleDTNode* parent, bool keep_gpu)
static void apple_boot_process_dt_node(AppleDTNode* node, AppleDTNode* parent, bool keep_gpu, bool audio)
{
GList* iter = NULL;
AppleDTNode* child = NULL;
Expand All @@ -302,7 +331,8 @@ static void apple_boot_process_dt_node(AppleDTNode* node, AppleDTNode* parent, b

if ((prop = apple_dt_get_prop(node, "compatible")) != NULL) {
assert_nonnull(prop->data);
found = keep_gpu && apple_boot_dt_match(prop, GPU_KEEP_COMP, ARRAY_SIZE(GPU_KEEP_COMP));
found = (keep_gpu && apple_boot_dt_match(prop, GPU_KEEP_COMP, ARRAY_SIZE(GPU_KEEP_COMP)))
|| (audio && apple_boot_dt_match(prop, AUDIO_COMP, ARRAY_SIZE(AUDIO_COMP)));
for (i = 0; !found && i < ARRAY_SIZE(KEEP_COMP); i++) {
if (memcmp(prop->data, KEEP_COMP[i], MIN(prop->len, sstrlen(KEEP_COMP[i]))) == 0) {
found = true;
Expand All @@ -322,7 +352,9 @@ static void apple_boot_process_dt_node(AppleDTNode* node, AppleDTNode* parent, b
&& !(keep_gpu && apple_boot_dt_match(prop, GPU_KEEP_NAMES, ARRAY_SIZE(GPU_KEEP_NAMES))))
{
assert_nonnull(prop->data);
for (i = 0; i < ARRAY_SIZE(REM_NAMES); i++) {
bool keep_name = audio && apple_boot_dt_match(prop, AUDIO_KEEP_NAMES, ARRAY_SIZE(AUDIO_KEEP_NAMES));

for (i = 0; !keep_name && i < ARRAY_SIZE(REM_NAMES); i++) {
uint64_t size = MIN(prop->len, sstrlen(REM_NAMES[i]));
if (memcmp(prop->data, REM_NAMES[i], size) == 0) {
assert_nonnull(parent);
Expand Down Expand Up @@ -359,7 +391,7 @@ static void apple_boot_process_dt_node(AppleDTNode* node, AppleDTNode* parent, b

// iter might get invalidated
iter = iter->next;
apple_boot_process_dt_node(child, node, keep_gpu);
apple_boot_process_dt_node(child, node, keep_gpu, audio);
}
}

Expand Down Expand Up @@ -617,7 +649,7 @@ void apple_boot_populate_dt(AppleDTNode* root, AppleBootInfo* info, bool auto_bo

apple_boot_init_mem_ranges(root);

apple_boot_process_dt_node(root, NULL, info->keep_gpu);
apple_boot_process_dt_node(root, NULL, info->keep_gpu, info->audio);

// Prevent further additions.
info->device_tree_size = ROUND_UP_16K(apple_dt_finalise(root));
Expand Down
141 changes: 132 additions & 9 deletions hw/arm/t8030.c
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,7 @@ static void t8030_memory_setup(AppleT8030MachineState* t8030)
if (t8030->sptm != NULL) { apple_sptm_allocate_memory_map(memory_map, &t8030->sptm_layout); }

info->keep_gpu = t8030->gpu;
info->audio = t8030->audio;
apple_boot_populate_dt(t8030->device_tree, info, auto_boot);

switch (hdr->file_type) {
Expand Down Expand Up @@ -2973,10 +2974,14 @@ static void t8030_create_aop(AppleT8030MachineState* t8030)
assert_nonnull(dma_mr);
object_property_add_const_link(OBJECT(aop), "dma-mr", OBJECT(dma_mr));

// sbd = apple_aop_audio_create(APPLE_AOP(aop));
// assert_nonnull(sbd);
// object_property_add_child(OBJECT(aop), "aop-audio", OBJECT(sbd));
// sysbus_realize_and_unref(sbd, &error_fatal);
// The AOP audio service goes with the audio device-tree nodes (audio=on):
// a machine without sound gets neither, and the drivers behind them never load.
if (t8030->audio) {
sbd = apple_aop_audio_create(APPLE_AOP(aop));
assert_nonnull(sbd);
object_property_add_child(OBJECT(aop), "aop-audio", OBJECT(sbd));
sysbus_realize_and_unref(sbd, &error_fatal);
}

sysbus_realize_and_unref(aop, &error_fatal);
}
Expand Down Expand Up @@ -3045,10 +3050,108 @@ static void t8030_create_mca(AppleT8030MachineState* t8030)
sysbus_mmio_map(sbd, APPLE_MCA_MMIO_DMA, t8030->armio_base + ldq_le_p(prop->data + sizeof(uint64_t) * 2));
sysbus_mmio_map(sbd, APPLE_MCA_MMIO_MCLK_CFG, t8030->armio_base + ldq_le_p(prop->data + sizeof(uint64_t) * 4));

if (t8030->audiodev != NULL) { qdev_prop_set_string(DEVICE(sbd), "audiodev", t8030->audiodev); }

object_property_add_child(OBJECT(t8030), "mca", OBJECT(sbd));
sysbus_realize_and_unref(sbd, &error_fatal);
}

/*
* audio=on: every I2S port has a SIO DMA channel, but only the speaker's
* (0x7A/0x7B, read by the MCA model) has anything consuming it. The audio
* server starts every member of an aggregate at once (codec, hawking,
* loopback, the actuator), and a transfer queued on a channel nobody reads
* never completes: its IO thread sleeps uninterruptibly in the kernel, the
* server misses its RPCs and the watchdog kills it, taking every sound in the
* system with it. So the other ports are drained here at about the rate a
* real one consumes: playback is taken and dropped, capture gets silence.
* From MakrSas/Inferno (branch ios, "audio: give the guest an output route").
*/
#define AUDIO_DRAIN_INTERVAL_NS (5 * SCALE_MS)
#define AUDIO_DRAIN_BYTES (48000 * 2 * 2 / 200)
#define AUDIO_DRAIN_MAX_PORTS (16)
#define AUDIO_DRAIN_SKIP_TX (0x7A)
#define AUDIO_DRAIN_SKIP_RX (0x7B)

static const char* AUDIO_I2S_NODES[] = {
"arm-io/mca0/mca0a", "arm-io/mca0/mca0b", "arm-io/mca1/mca1a", "arm-io/mca1/mca1b",
"arm-io/mca2/mca2a", "arm-io/mca2/mca2b", "arm-io/mca3/mca3a", "arm-io/mca3/mca3b",
"arm-io/mca4/mca4a", "arm-io/mca4/mca4b", "arm-io/mca5/mca5a", "arm-io/mca5/mca5b",
"arm-io/alc0", "arm-io/alc1", "arm-io/alc2", "arm-io/alc3",
};

typedef struct
{
AppleSIODMAEndpoint* tx[AUDIO_DRAIN_MAX_PORTS];
AppleSIODMAEndpoint* rx[AUDIO_DRAIN_MAX_PORTS];
size_t count;
QEMUTimer* timer;
} T8030AudioDrain;

static void t8030_audio_drain(void* opaque)
{
T8030AudioDrain* drain = opaque;
uint8_t buffer[AUDIO_DRAIN_BYTES];
size_t i;

for (i = 0; i < drain->count; i++) {
if (drain->tx[i] != NULL) { apple_sio_dma_read(drain->tx[i], buffer, sizeof(buffer)); }
if (drain->rx[i] != NULL) { apple_sio_dma_blit(drain->rx[i], 0, sizeof(buffer)); }
}

timer_mod_ns(drain->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + AUDIO_DRAIN_INTERVAL_NS);
}

static void t8030_create_audio_drain(AppleT8030MachineState* t8030)
{
AppleSIO* sio;
T8030AudioDrain* drain;
AppleSIODMAEndpoint* skip_tx;
AppleSIODMAEndpoint* skip_rx;
size_t i;

sio = APPLE_SIO(object_property_get_link(OBJECT(t8030), "sio", &error_fatal));
skip_tx = apple_sio_get_endpoint(sio, AUDIO_DRAIN_SKIP_TX);
skip_rx = apple_sio_get_endpoint(sio, AUDIO_DRAIN_SKIP_RX);
drain = g_new0(T8030AudioDrain, 1);

for (i = 0; i < ARRAY_SIZE(AUDIO_I2S_NODES) && drain->count < AUDIO_DRAIN_MAX_PORTS; i++) {
AppleDTNode* node;
AppleDTProp* prop;
AppleSIODMAEndpoint* tx;
AppleSIODMAEndpoint* rx;
uint8_t channel;

node = apple_dt_get_node(t8030->device_tree, AUDIO_I2S_NODES[i]);
if (node == NULL) { continue; }

// The channel the node names, and the next one for the other direction,
// as t8030_create_mca() does for the speaker.
prop = apple_dt_get_prop(node, "dma-channels");
if (prop == NULL || prop->len == 0) { continue; }

channel = ((const uint8_t*)prop->data)[0];
tx = apple_sio_get_endpoint(sio, channel);
rx = apple_sio_get_endpoint(sio, channel + 1);

if (tx == skip_tx || tx == skip_rx) { tx = NULL; }
if (rx == skip_tx || rx == skip_rx) { rx = NULL; }
if (tx == NULL && rx == NULL) { continue; }

drain->tx[drain->count] = tx;
drain->rx[drain->count] = rx;
drain->count++;
}

if (drain->count == 0) {
g_free(drain);
return;
}

drain->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, t8030_audio_drain, drain);
timer_mod_ns(drain->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + AUDIO_DRAIN_INTERVAL_NS);
}

static void t8030_create_speaker_top(AppleT8030MachineState* t8030)
{
AppleDTNode* child;
Expand Down Expand Up @@ -3343,9 +3446,16 @@ static void t8030_create_devices(AppleT8030MachineState* t8030)
t8030_create_mt_spi(t8030);
t8030_create_aop(t8030);
if (t8030->gpu) { t8030_create_gpu(t8030); }
t8030_create_mca(t8030);
t8030_create_speaker_top(t8030);
t8030_create_speaker_bottom(t8030);
if (t8030->audio) {
t8030_create_mca(t8030);
t8030_create_speaker_top(t8030);
t8030_create_speaker_bottom(t8030);
t8030_create_audio_drain(t8030);
}
else if (t8030->audiodev != NULL) {
error_setg(&error_fatal, "audiodev= needs audio=on");
return;
}
t8030_create_buttons(t8030);
t8030_create_mipi_dsim(t8030);
t8030_create_scaler(t8030);
Expand Down Expand Up @@ -3421,8 +3531,14 @@ static void t8101_create_devices(AppleT8030MachineState* t8030)
t8030_create_roswell(t8030);
t8030_create_display(t8030);
t8030_create_aop(t8030);
t8030_create_mca(t8030);
t8030_create_speaker_top(t8030);
if (t8030->audio) {
t8030_create_mca(t8030);
t8030_create_speaker_top(t8030);
}
else if (t8030->audiodev != NULL) {
error_setg(&error_fatal, "audiodev= needs audio=on");
return;
}
t8030_create_buttons(t8030);
t8030_create_scaler(t8030);
t8030_create_accbuck(t8030);
Expand Down Expand Up @@ -3728,6 +3844,8 @@ PROP_VISIT_GETTER_SETTER(uint32, disp_height);
PROP_GETTER_SETTER(bool, enable_pac);
PROP_GETTER_SETTER(bool, patch_check);
PROP_GETTER_SETTER(bool, gpu);
PROP_GETTER_SETTER(bool, audio);
PROP_STR_GETTER_SETTER(audiodev);

static void apple_soc_machine_class_init(ObjectClass* klass, const AppleSoCConfig* soc)
{
Expand Down Expand Up @@ -3809,6 +3927,11 @@ static void apple_soc_machine_class_init(ObjectClass* klass, const AppleSoCConfi
object_class_property_set_description(klass, "gpu",
"Expose the AGX GPU and emulate its coprocessor's firmware "
"(experimental, docs/GPU-EMULATION-SCOPE.md). Default off.");
object_class_property_add_bool(klass, "audio", t8030_get_audio, t8030_set_audio);
object_class_property_set_description(klass, "audio",
"Expose the audio hardware (MCA, CS42L77 codec, CS35L27 amp) to iOS");
object_class_property_add_str(klass, "audiodev", t8030_get_audiodev, t8030_set_audiodev);
object_class_property_set_description(klass, "audiodev", "ID of the -audiodev that plays the speaker output");
}

static void t8030_class_init(ObjectClass* klass, const void* data) { apple_soc_machine_class_init(klass, &t8030_soc); }
Expand Down
Loading
Loading