/*
sound-hda.h - HD Audio
Copyright (C) 2025  David Korenchuk <github.com/epoll-reactor-2>

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

#ifndef SOUND_HDA_H
#define SOUND_HDA_H

#include "rvvmlib.h"
#include "pci-bus.h"

typedef struct sound_subsystem_t sound_subsystem_t;

struct sound_subsystem_t {
    void *sound_data;
    void (*write)(sound_subsystem_t *subsystem, void *data, size_t size);
    // Optional. Pulls up to `size` bytes of mono S16LE PCM into `data`,
    // returns the count actually filled. Used by HDA's input stream
    // drain to fetch capture data from the host.
    //
    // CONTRACT: must be non-blocking. If the backend has nothing
    // available, return 0 — the input drain pads the rest of the
    // request with silence and keeps the guest's stream rate stable.
    // Blocking here would distort the guest's perceived sample clock.
    // NULL = no capture support; the input stream worker still runs
    // (so the guest's BDL bookkeeping isn't disturbed) but writes
    // silence the entire time.
    size_t (*read)(sound_subsystem_t *subsystem, void *data, size_t size);
    // Optional. Called from sound_hda_remove() before it waits for the
    // stream worker to exit. Backends whose write() / read() can block
    // (host ALSA PCM, any IPC sink without an internal queue) must
    // implement this so a blocked worker unblocks quickly — otherwise
    // remove() waits on a wedged backend while the caller expects to
    // free the machine. Non-blocking backends (ring buffers, capture
    // fixtures) can leave this NULL. After abort() returns, subsequent
    // write() / read() calls are allowed to be no-ops; the worker is
    // tearing down.
    void (*abort)(sound_subsystem_t *subsystem);
    // Optional. The HDA stream worker calls this from its own thread
    // before write() whenever the guest's configured sample rate
    // (parsed from SDnFMT) differs from the rate previously announced
    // to the backend. Backends with a fixed host PCM (ALSA) should
    // close-and-reopen at the new rate; backends that resample
    // internally can no-op.
    //
    // Invoked once per drain entry; idempotent calls with the same
    // rate must be cheap. Always paired with the existing mono /
    // 16-bit-LE write contract — only the rate varies.
    //
    // NULL = stick with whatever rate was set at init(). Common case
    // before this hook existed: 48 kHz hardcoded, which mis-paces
    // every guest that streams at 44.1 kHz.
    void (*set_rate)(sound_subsystem_t *subsystem, uint32_t rate_hz);
};

/*
 * Public callback type for library consumers who want to install their own
 * host-side audio sink without going through a compile-time USE_* backend.
 *
 * The HDA stream worker invokes this callback on its own thread with a chunk
 * of 16-bit signed little-endian PCM data — mono, 48 kHz, matching the
 * format the codec advertises. `user_data` is whatever pointer was passed
 * to sound_hda_init_ex / sound_hda_init_auto_ex.
 *
 * Useful for embedders routing audio into a non-native audio stack (a
 * managed runtime, an IPC channel, a WAV capture test harness, etc.)
 * without relying on a backend compiled into librvvm.
 */
typedef void (*sound_hda_backend_write_fn)(void *user_data, void *pcm_data, size_t size);

/*
 * Optional abort callback paired with sound_hda_backend_write_fn. When the
 * HDA device is being removed (machine teardown), the stream worker may be
 * blocked inside write_fn. abort_fn is invoked on the teardown thread and
 * must cause any in-flight or future write_fn call on this sink to return
 * promptly — typically by setting a flag that write_fn checks, or by
 * signalling whatever the sink is blocked on (closing an fd, dropping a
 * host PCM, etc.). Leave NULL for non-blocking sinks (ring buffers, etc.).
 */
typedef void (*sound_hda_backend_abort_fn)(void *user_data);

// Internal use
bool alsa_sound_init(sound_subsystem_t *sound);

PUBLIC pci_dev_t* sound_hda_init(pci_bus_t* pci_bus);
PUBLIC pci_dev_t* sound_hda_init_auto(rvvm_machine_t* machine);

/*
 * Extended init variants that let the caller plug in a custom host-side
 * audio sink. Pass NULL for `write_fn` to fall back to the compile-time
 * default (USE_ALSA if enabled, silent otherwise — same behaviour as
 * sound_hda_init / sound_hda_init_auto).
 *
 * When a non-NULL `write_fn` is supplied, the HDA device skips any
 * compiled-in backend and routes every PCM chunk through `write_fn`.
 *
 * `abort_fn` is optional. Supply it when `write_fn` can block; it is
 * invoked during device removal to unblock any in-flight write so the
 * stream worker can exit promptly. Pass NULL if `write_fn` never blocks.
 */
PUBLIC pci_dev_t* sound_hda_init_ex(pci_bus_t* pci_bus,
                                    sound_hda_backend_write_fn write_fn,
                                    sound_hda_backend_abort_fn abort_fn,
                                    void* user_data);
PUBLIC pci_dev_t* sound_hda_init_auto_ex(rvvm_machine_t* machine,
                                         sound_hda_backend_write_fn write_fn,
                                         sound_hda_backend_abort_fn abort_fn,
                                         void* user_data);

#endif
