From 49caf7c7d4b5d23ad0e9c9d80ced3eac4e56a65b Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Sun, 10 May 2026 02:37:07 +0500 Subject: [PATCH 01/15] x86-64: intel-hda major fixes --- drivers/intel-hda.lisp | 132 ++++++++++++++++++++++++++++------------- 1 file changed, 91 insertions(+), 41 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index 25254324e..c39b6f124 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -199,6 +199,8 @@ (defconstant +dmap-entry-size+ 4) (defconstant +bdl-entry-size+ 16) (defconstant +max-bdl-entries+ 256) +(defconstant +playback-period-bytes+ #x0800) +(defconstant +playback-period-count+ 4) (defconstant +corb-offset+ 0) (defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+)) @@ -929,7 +931,8 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (sd-reg/32 hda stream-id +sdnbdpl+) (ldb (byte 32 0) bdl) (sd-reg/32 hda stream-id +sdnbdpu+) (ldb (byte 32 32) bdl)) (setf (sd-reg/16 hda stream-id +sdnfmt+) #x4011 - (sd-reg/16 hda stream-id +sdnlvi+) bdl-length + ;; LVI is the last valid descriptor index, not the descriptor count. + (sd-reg/16 hda stream-id +sdnlvi+) (1- bdl-length) (sd-reg/32 hda stream-id +sdncbl+) cb-length))) (defun stream-reset (hda stream-id) @@ -955,41 +958,52 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (defun first-output-stream (hda) (with-hda-access (hda) - (gcap-iss (global-reg/16 hda +gcap+)))) + (gcap-oss (global-reg/16 hda +gcap+)))) (defun start-playback (hda buffer buffer-size codec dac pin &optional mixer) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) - (write-bdl hda 0 buffer (truncate buffer-size 2)) - (write-bdl hda 1 (+ buffer (truncate buffer-size 2)) (truncate buffer-size 2)) - (prep-stream hda (first-output-stream hda) 0 1 buffer-size) + (dotimes (i +playback-period-count+) + (write-bdl hda i + (+ buffer (* i +playback-period-bytes+)) + +playback-period-bytes+)) + (prep-stream hda (first-output-stream hda) 0 +playback-period-count+ buffer-size) (when mixer (command hda codec mixer #x3F07F)) ; unmute L/R out/in (command hda codec dac #x70610) ; stream=1 - (command hda codec dac #x24011) ; format - (command hda codec dac #x3b07f) ; unmute L/R out - (command hda codec pin #x3b07f) ; unmute L/R out - (command hda codec pin #x70740) ; enable output - ;; Enable global and stream interrupts. - (setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda)))) - (stream-go hda (first-output-stream hda)))) + (command hda codec dac #x24011) ; format + (command hda codec dac #x3b07f) ; unmute L/R out + (command hda codec pin #x3b07f) ; unmute L/R out + (command hda codec pin #x70740) ; enable output + ;; Enable global and stream interrupts. + (setf (global-reg/32 hda +intctl+) (logior #xC0000000 (ash 1 (first-output-stream hda)))) + (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) - ;; qemu is picky and requires a write to the 8-bit status part of - ;; the register. - (setf (sd-reg/8 hda stream (+ +sdnctlsts+ 3)) (ash 1 2))) ; bcis + ;; Clear any latched stream status in the status byte. qemu is picky and + ;; requires a write to the 8-bit status part of the register. + (setf (sd-reg/8 hda stream (+ +sdnctlsts+ 3)) + (logior (ash 1 4) ; dese + (ash 1 3) ; fifoe + (ash 1 2)))) ; bcis (defun wait-for-buffer-interrupt (hda) (let ((irq (hda-irq hda)) (stream (first-output-stream hda))) (loop - (sync:wait-for-objects irq (pci:pci-device-boot-id (hda-pci-device hda))) + (multiple-value-bind (objects remaining) + (sync:wait-for-objects-with-timeout 0.02 + irq + (pci:pci-device-boot-id (hda-pci-device hda))) + (declare (ignore remaining)) + (when (null objects) + (return :timeout))) (with-hda-access (hda) (when (logbitp stream (global-reg/32 hda +intsts+)) (clear-pending-interrupt hda stream) (mezzano.supervisor:simple-irq-unmask irq) - (return))) - (mezzano.supervisor:simple-irq-unmask irq)))) + (return :interrupt))) + (mezzano.supervisor:simple-irq-unmask irq)))) ;; Return a list of all pin widgets. (defun pin-widgets (hda) @@ -1041,19 +1055,40 @@ Returns NIL if there is no output path." (t (values (first direct-converters) nil))))) +(defun set-widget-power-state-d0 (node) + ;; Set Power State = D0. + (command (hda node) (cad node) (nid node) #x70500)) + +(defun maybe-enable-eapd (widget) + (when (typep widget 'audio-pin-complex) + (let ((caps (parameter widget +parameter-pin-capabilities+))) + (when (not (zerop (parameter-pin-capabilities-eapd-capable caps))) + ;; Enable EAPD/BTL. + (command (hda widget) (cad widget) (nid widget) #x70C02))))) + +(defun log-selected-output-path (pin converter mixer) + (mezzano.supervisor:debug-print-line + "HDA selected pin " (nid pin) + " (" (pin-default-device pin) " " (pin-location pin) " " (pin-colour pin) ")" + " converter " (nid converter) + (if mixer + (format nil " mixer ~D" (nid mixer)) + ""))) + ;; TODO: This should stream to anything that looks vaugely output-like, instead ;; of a single pin. (defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback) (handler-case (let* ((buffer (hda-dma-buffer-phys hda)) - (buf-len #x2000);(hda-dma-buffer-size hda)) - (half-buf-len (truncate buf-len 2)) - (n-samples (truncate half-buf-len 2)) ; buf-len is in bytes (2 per sample) and we want to fill only half the buffer at once + (buf-len (* +playback-period-bytes+ +playback-period-count+)) + (n-samples (truncate +playback-period-bytes+ 2)) ; bytes to stereo 16-bit samples (float-sample-buffer (make-array n-samples :element-type 'single-float)) (output-pin (default-output-pin hda)) (output-stream (first-output-stream hda)) (buffer-offset 0) (stop-countdown nil)) + (unless output-pin + (error "No HDA output pin with a playback path found.")) (labels ((store-sample (sample offset) ;; Clamp to limits, don't wrap. (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) @@ -1067,41 +1102,55 @@ Returns NIL if there is no output path." (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) (refill-fifo () - (with-hda-access (hda) - (locally - (declare (optimize speed (safety 0)) - (type (simple-array single-float (*)) float-sample-buffer) - (type fixnum n-samples)) - (dotimes (i n-samples) - (store-sample (aref float-sample-buffer i) i)))) - (cond ((eql buffer-offset 0) - (setf buffer-offset half-buf-len)) - (t - (setf buffer-offset 0))) (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) (setf stop-countdown nil)) ((not stop-countdown) - (setf stop-countdown 4))))) + (setf stop-countdown 4))) + (with-hda-access (hda) + (locally + (declare (optimize speed (safety 0)) + (type (simple-array single-float (*)) float-sample-buffer) + (type fixnum n-samples)) + (dotimes (i n-samples) + (store-sample (aref float-sample-buffer i) i)))) + (setf buffer-offset (rem (+ buffer-offset +playback-period-bytes+) + buf-len)))) ;; Prepopulate the initial buffer. - (funcall buffer-fill-callback float-sample-buffer 0 n-samples) + (dotimes (i +playback-period-count+) + (refill-fifo)) (with-hda-access (hda) - ;; Clear the whole buffer. - (dotimes (i buf-len) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) ;; Unmask the interrupt, flush any pending IRQs. (clear-pending-interrupt hda output-stream) (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) ;; Begin playback. (multiple-value-bind (converter mixer) (output-path output-pin) + (unless converter + (error "HDA output pin ~D has no converter." (nid output-pin))) + (log-selected-output-path output-pin converter mixer) + (set-widget-power-state-d0 output-pin) + (set-widget-power-state-d0 converter) + (when mixer + (set-widget-power-state-d0 mixer)) + (maybe-enable-eapd output-pin) (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)))) (unwind-protect (loop ;; Wait for the dma position to move from the - ;; current buffer to the other buffer. + ;; current period to the next period. (let* ((dmap (dma-position hda output-stream)) - (current-offset (truncate dmap half-buf-len))) - (when (not (eql current-offset (truncate buffer-offset half-buf-len))) + (lpib (sd-reg/32 hda output-stream +sdnlpib+)) + (sts (sd-reg/32 hda output-stream +sdnctlsts+)) + (current-offset (truncate dmap +playback-period-bytes+))) + (when (and (zerop dmap) + (zerop lpib) + (not (logtest sts (ash 1 1)))) + (mezzano.supervisor:debug-print-line + "HDA stream stalled: dmap=" dmap + " lpib=" lpib + " ctlsts=#x" sts + " intsts=#x" (global-reg/32 hda +intsts+))) + (when (not (eql current-offset (truncate buffer-offset +playback-period-bytes+))) (when stop-countdown (when (zerop stop-countdown) (return)) @@ -1121,5 +1170,6 @@ Returns NIL if there is no output path." (pci:define-pci-driver intel-hda intel-hda-probe ((#x8086 #x2668) ; ICH6 HDA - (#x8086 #x27D8)) ; ICH7 HDA + (#x8086 #x27D8) ; ICH7 HDA + (#x8086 #x293E)) ; ICH9 HDA ()) From 5190cad3f8ef2b8e963b05e8813caf30dc7fb8ce Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Sun, 10 May 2026 12:33:41 +0500 Subject: [PATCH 02/15] intel-hda: Low buffer size works. --- drivers/intel-hda.lisp | 4 ++-- gui/music-player.lisp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index c39b6f124..e04a0e5e0 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -199,8 +199,8 @@ (defconstant +dmap-entry-size+ 4) (defconstant +bdl-entry-size+ 16) (defconstant +max-bdl-entries+ 256) -(defconstant +playback-period-bytes+ #x0800) -(defconstant +playback-period-count+ 4) +(defconstant +playback-period-bytes+ #x0100) +(defconstant +playback-period-count+ 3) (defconstant +corb-offset+ 0) (defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+)) diff --git a/gui/music-player.lisp b/gui/music-player.lisp index f25bd74d4..409945e15 100644 --- a/gui/music-player.lisp +++ b/gui/music-player.lisp @@ -53,7 +53,7 @@ (defun worker (player) (unwind-protect - (let* ((data-buffer (make-array #x2000 :element-type '(unsigned-byte 8))) + (let* ((data-buffer (make-array #x200 :element-type '(unsigned-byte 8))) (stream (player-stream player)) (riff-chunks (wav:read-wav-file stream :chunk-data-reader (wrap-data-chunk-data-samples-reader))) @@ -85,13 +85,13 @@ (member sample-size '(8 16)))) (format t "Unsupported wav file. ~S ~S ~S ~S ~S~%" fmt compression channels sample-rate sample-size) (return-from worker)) - (file-position stream data-file-position) - (let ((audio-sink (mezzano.driver.sound:make-sound-output-sink - :buffer-duration 1.0 - :format (ecase sample-size - (8 :pcm-u8) - (16 :pcm-s16le)))) - (current-position 0)) + (file-position stream data-file-position) + (let ((audio-sink (mezzano.driver.sound:make-sound-output-sink + :buffer-duration 0.05 + :format (ecase sample-size + (8 :pcm-u8) + (16 :pcm-s16le)))) + (current-position 0)) (unwind-protect (loop (when (>= current-position data-size) From 117e0f4adfeb9ff07025b53a9f0ec54d732c3d82 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Sun, 10 May 2026 13:15:15 +0500 Subject: [PATCH 03/15] intel-hda: dynamically compute buffer size --- drivers/intel-hda.lisp | 206 ++++++++++++++++++++++------------------- 1 file changed, 112 insertions(+), 94 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index e04a0e5e0..fbfaebad2 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -926,11 +926,11 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (virt (+ mezzano.supervisor::+physical-map-base+ phys))) (setf (sys.int::memref-unsigned-byte-32 virt (* stream 2)) value))) -(defun prep-stream (hda stream-id bdl-base bdl-length cb-length) +(defun prep-stream (hda stream-id bdl-base bdl-length cb-length &optional (sdnfmt #x4011)) (let ((bdl (+ (hda-corb/rirb/dmap-physical hda) +bdl-offset+ (* bdl-base 16)))) (setf (sd-reg/32 hda stream-id +sdnbdpl+) (ldb (byte 32 0) bdl) (sd-reg/32 hda stream-id +sdnbdpu+) (ldb (byte 32 32) bdl)) - (setf (sd-reg/16 hda stream-id +sdnfmt+) #x4011 + (setf (sd-reg/16 hda stream-id +sdnfmt+) sdnfmt ;; LVI is the last valid descriptor index, not the descriptor count. (sd-reg/16 hda stream-id +sdnlvi+) (1- bdl-length) (sd-reg/32 hda stream-id +sdncbl+) cb-length))) @@ -960,24 +960,28 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (with-hda-access (hda) (gcap-oss (global-reg/16 hda +gcap+)))) -(defun start-playback (hda buffer buffer-size codec dac pin &optional mixer) +(defun start-playback (hda buffer buffer-size codec dac pin &optional mixer + &key + (period-bytes +playback-period-bytes+) + (period-count +playback-period-count+) + (sdnfmt #x4011)) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) - (dotimes (i +playback-period-count+) + (dotimes (i period-count) (write-bdl hda i - (+ buffer (* i +playback-period-bytes+)) - +playback-period-bytes+)) - (prep-stream hda (first-output-stream hda) 0 +playback-period-count+ buffer-size) + (+ buffer (* i period-bytes)) + period-bytes)) + (prep-stream hda (first-output-stream hda) 0 period-count buffer-size sdnfmt) (when mixer (command hda codec mixer #x3F07F)) ; unmute L/R out/in (command hda codec dac #x70610) ; stream=1 - (command hda codec dac #x24011) ; format - (command hda codec dac #x3b07f) ; unmute L/R out - (command hda codec pin #x3b07f) ; unmute L/R out - (command hda codec pin #x70740) ; enable output - ;; Enable global and stream interrupts. - (setf (global-reg/32 hda +intctl+) (logior #xC0000000 (ash 1 (first-output-stream hda)))) - (stream-go hda (first-output-stream hda)))) + (command hda codec dac (logior (ash #x2 16) sdnfmt)) ; format + (command hda codec dac #x3b07f) ; unmute L/R out + (command hda codec pin #x3b07f) ; unmute L/R out + (command hda codec pin #x70740) ; enable output + ;; Enable global and stream interrupts. + (setf (global-reg/32 hda +intctl+) (logior #xC0000000 (ash 1 (first-output-stream hda)))) + (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) ;; Clear any latched stream status in the status byte. qemu is picky and @@ -1079,88 +1083,102 @@ Returns NIL if there is no output path." ;; of a single pin. (defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback) (handler-case - (let* ((buffer (hda-dma-buffer-phys hda)) - (buf-len (* +playback-period-bytes+ +playback-period-count+)) - (n-samples (truncate +playback-period-bytes+ 2)) ; bytes to stereo 16-bit samples - (float-sample-buffer (make-array n-samples :element-type 'single-float)) - (output-pin (default-output-pin hda)) - (output-stream (first-output-stream hda)) - (buffer-offset 0) - (stop-countdown nil)) + (let* ((output-pin (default-output-pin hda)) + (output-stream (first-output-stream hda))) (unless output-pin (error "No HDA output pin with a playback path found.")) - (labels ((store-sample (sample offset) - ;; Clamp to limits, don't wrap. - (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) - (sample-rescaled (if (< sample-clamped 0.0f0) - (* sample-clamped 32768.0f0) - (* sample-clamped 32767.0f0))) - (sample-16bit (truncate sample-rescaled))) - (declare (optimize speed (safety 0)) - (type single-float sample-clamped sample-rescaled) - (type fixnum sample-16bit)) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) - (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) - (refill-fifo () - (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) - (setf stop-countdown nil)) - ((not stop-countdown) - (setf stop-countdown 4))) - (with-hda-access (hda) - (locally - (declare (optimize speed (safety 0)) - (type (simple-array single-float (*)) float-sample-buffer) - (type fixnum n-samples)) - (dotimes (i n-samples) - (store-sample (aref float-sample-buffer i) i)))) - (setf buffer-offset (rem (+ buffer-offset +playback-period-bytes+) - buf-len)))) - ;; Prepopulate the initial buffer. - (dotimes (i +playback-period-count+) - (refill-fifo)) - (with-hda-access (hda) - ;; Unmask the interrupt, flush any pending IRQs. - (clear-pending-interrupt hda output-stream) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) - ;; Begin playback. - (multiple-value-bind (converter mixer) - (output-path output-pin) - (unless converter - (error "HDA output pin ~D has no converter." (nid output-pin))) - (log-selected-output-path output-pin converter mixer) - (set-widget-power-state-d0 output-pin) - (set-widget-power-state-d0 converter) - (when mixer - (set-widget-power-state-d0 mixer)) - (maybe-enable-eapd output-pin) - (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)))) - (unwind-protect - (loop - ;; Wait for the dma position to move from the - ;; current period to the next period. - (let* ((dmap (dma-position hda output-stream)) - (lpib (sd-reg/32 hda output-stream +sdnlpib+)) - (sts (sd-reg/32 hda output-stream +sdnctlsts+)) - (current-offset (truncate dmap +playback-period-bytes+))) - (when (and (zerop dmap) - (zerop lpib) - (not (logtest sts (ash 1 1)))) - (mezzano.supervisor:debug-print-line - "HDA stream stalled: dmap=" dmap - " lpib=" lpib - " ctlsts=#x" sts - " intsts=#x" (global-reg/32 hda +intsts+))) - (when (not (eql current-offset (truncate buffer-offset +playback-period-bytes+))) - (when stop-countdown - (when (zerop stop-countdown) - (return)) - (decf stop-countdown)) - ;; Refill buffer. - (refill-fifo))) - (wait-for-buffer-interrupt hda)) - (with-hda-access (hda) - (stream-reset hda (first-output-stream hda)) - (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) + (multiple-value-bind (converter mixer) + (output-path output-pin) + (unless converter + (error "HDA output pin ~D has no converter." (nid output-pin))) + (log-selected-output-path output-pin converter mixer) + (set-widget-power-state-d0 output-pin) + (set-widget-power-state-d0 converter) + (when mixer + (set-widget-power-state-d0 mixer)) + (maybe-enable-eapd output-pin) + ;; Determine buffer sizes from the stream's FIFO size. + ;; SDnFIFOS reports FIFO depth in DWords (32-bit units). + ;; Cap the FIFO at a realistic 64 DWords (256 bytes) — QEMU + ;; reports 256 DWords but no real HDA output stream has that. + (let* ((raw-fifo-dwords (sd-reg/16 hda output-stream +sdnfifos+)) + (fifo-dwords (min raw-fifo-dwords 64)) + (period-bytes (logand (+ (max (* fifo-dwords 4) 256) 127) + (lognot 127))) + (period-count 3) + (buf-len (* period-bytes period-count)) + (n-samples (truncate period-bytes 2)) ; bytes to 16-bit samples + (float-sample-buffer (make-array n-samples :element-type 'single-float)) + (buffer (hda-dma-buffer-phys hda)) + (buffer-offset 0) + (stop-countdown nil)) + (mezzano.supervisor:debug-print-line + "HDA FIFO " raw-fifo-dwords " DWords (capped to " fifo-dwords + "), period " period-bytes " B × " period-count " = " buf-len " B total") + (start-playback hda buffer buf-len + (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) + :period-bytes period-bytes + :period-count period-count) + (labels ((store-sample (sample offset) + ;; Clamp to limits, don't wrap. + (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) + (sample-rescaled (if (< sample-clamped 0.0f0) + (* sample-clamped 32768.0f0) + (* sample-clamped 32767.0f0))) + (sample-16bit (truncate sample-rescaled))) + (declare (optimize speed (safety 0)) + (type single-float sample-clamped sample-rescaled) + (type fixnum sample-16bit)) + (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) + (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) + (refill-fifo () + (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) + (setf stop-countdown nil)) + ((not stop-countdown) + (setf stop-countdown 4))) + (with-hda-access (hda) + (locally + (declare (optimize speed (safety 0)) + (type (simple-array single-float (*)) float-sample-buffer) + (type fixnum n-samples)) + (dotimes (i n-samples) + (store-sample (aref float-sample-buffer i) i)))) + (setf buffer-offset (rem (+ buffer-offset period-bytes) + buf-len)))) + ;; Prepopulate the initial buffer. + (dotimes (i period-count) + (refill-fifo)) + (with-hda-access (hda) + ;; Unmask the interrupt, flush any pending IRQs. + (clear-pending-interrupt hda output-stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) + (unwind-protect + (loop + ;; Wait for the dma position to move from the + ;; current period to the next period. + (let* ((dmap (dma-position hda output-stream)) + (lpib (sd-reg/32 hda output-stream +sdnlpib+)) + (sts (sd-reg/32 hda output-stream +sdnctlsts+)) + (current-offset (truncate dmap period-bytes))) + (when (and (zerop dmap) + (zerop lpib) + (not (logtest sts (ash 1 1)))) + (mezzano.supervisor:debug-print-line + "HDA stream stalled: dmap=" dmap + " lpib=" lpib + " ctlsts=#x" sts + " intsts=#x" (global-reg/32 hda +intsts+))) + (when (not (eql current-offset (truncate buffer-offset period-bytes))) + (when stop-countdown + (when (zerop stop-countdown) + (return)) + (decf stop-countdown)) + ;; Refill buffer. + (refill-fifo))) + (wait-for-buffer-interrupt hda)) + (with-hda-access (hda) + (stream-reset hda (first-output-stream hda)) + (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))))) (device-disconnect () (format t "HDA ~S disconnected.~%" hda) (throw 'mezzano.supervisor:terminate-thread nil)))) From af91ace2c1289d672c6a45f6fe6919a7ab3beb26 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Thu, 14 May 2026 03:20:46 +0500 Subject: [PATCH 04/15] intel-hda: underrun detection and tunable period size --- drivers/intel-hda.lisp | 230 ++++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 105 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index fbfaebad2..dc16c4c44 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -202,6 +202,11 @@ (defconstant +playback-period-bytes+ #x0100) (defconstant +playback-period-count+ 3) +(defparameter *hda-min-period-bytes* 512 + "Minimum size of each HDA audio period in bytes. Larger values reduce + glitching at the cost of higher latency. Must be a multiple of 128. + Default: 256 bytes = 64 stereo frames = ~1.45ms at 44100Hz.") + (defconstant +corb-offset+ 0) (defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+)) (defconstant +dmap-offset+ (+ +rirb-offset+ +rirb-max-size+)) @@ -926,11 +931,11 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (virt (+ mezzano.supervisor::+physical-map-base+ phys))) (setf (sys.int::memref-unsigned-byte-32 virt (* stream 2)) value))) -(defun prep-stream (hda stream-id bdl-base bdl-length cb-length &optional (sdnfmt #x4011)) +(defun prep-stream (hda stream-id bdl-base bdl-length cb-length) (let ((bdl (+ (hda-corb/rirb/dmap-physical hda) +bdl-offset+ (* bdl-base 16)))) (setf (sd-reg/32 hda stream-id +sdnbdpl+) (ldb (byte 32 0) bdl) (sd-reg/32 hda stream-id +sdnbdpu+) (ldb (byte 32 32) bdl)) - (setf (sd-reg/16 hda stream-id +sdnfmt+) sdnfmt + (setf (sd-reg/16 hda stream-id +sdnfmt+) #x4011 ;; LVI is the last valid descriptor index, not the descriptor count. (sd-reg/16 hda stream-id +sdnlvi+) (1- bdl-length) (sd-reg/32 hda stream-id +sdncbl+) cb-length))) @@ -963,25 +968,24 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (defun start-playback (hda buffer buffer-size codec dac pin &optional mixer &key (period-bytes +playback-period-bytes+) - (period-count +playback-period-count+) - (sdnfmt #x4011)) + (period-count +playback-period-count+)) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) (dotimes (i period-count) (write-bdl hda i (+ buffer (* i period-bytes)) period-bytes)) - (prep-stream hda (first-output-stream hda) 0 period-count buffer-size sdnfmt) + (prep-stream hda (first-output-stream hda) 0 period-count buffer-size) (when mixer (command hda codec mixer #x3F07F)) ; unmute L/R out/in (command hda codec dac #x70610) ; stream=1 - (command hda codec dac (logior (ash #x2 16) sdnfmt)) ; format - (command hda codec dac #x3b07f) ; unmute L/R out - (command hda codec pin #x3b07f) ; unmute L/R out - (command hda codec pin #x70740) ; enable output - ;; Enable global and stream interrupts. - (setf (global-reg/32 hda +intctl+) (logior #xC0000000 (ash 1 (first-output-stream hda)))) - (stream-go hda (first-output-stream hda)))) + (command hda codec dac #x24011) ; format + (command hda codec dac #x3b07f) ; unmute L/R out + (command hda codec pin #x3b07f) ; unmute L/R out + (command hda codec pin #x70740) ; enable output + ;; Enable global and stream interrupts. + (setf (global-reg/32 hda +intctl+) (logior #xC0000000 (ash 1 (first-output-stream hda)))) + (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) ;; Clear any latched stream status in the status byte. qemu is picky and @@ -1083,102 +1087,118 @@ Returns NIL if there is no output path." ;; of a single pin. (defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback) (handler-case - (let* ((output-pin (default-output-pin hda)) - (output-stream (first-output-stream hda))) + (let* ((buffer (hda-dma-buffer-phys hda)) + (period-bytes (max +playback-period-bytes+ *hda-min-period-bytes*)) + (period-count +playback-period-count+) + (buf-len (* period-bytes period-count)) + (n-samples (truncate period-bytes 2)) ; bytes to stereo 16-bit samples + (float-sample-buffer (make-array n-samples :element-type 'single-float)) + (output-pin (default-output-pin hda)) + (output-stream (first-output-stream hda)) + (buffer-offset 0) + (stop-countdown nil)) (unless output-pin (error "No HDA output pin with a playback path found.")) - (multiple-value-bind (converter mixer) - (output-path output-pin) - (unless converter - (error "HDA output pin ~D has no converter." (nid output-pin))) - (log-selected-output-path output-pin converter mixer) - (set-widget-power-state-d0 output-pin) - (set-widget-power-state-d0 converter) - (when mixer - (set-widget-power-state-d0 mixer)) - (maybe-enable-eapd output-pin) - ;; Determine buffer sizes from the stream's FIFO size. - ;; SDnFIFOS reports FIFO depth in DWords (32-bit units). - ;; Cap the FIFO at a realistic 64 DWords (256 bytes) — QEMU - ;; reports 256 DWords but no real HDA output stream has that. - (let* ((raw-fifo-dwords (sd-reg/16 hda output-stream +sdnfifos+)) - (fifo-dwords (min raw-fifo-dwords 64)) - (period-bytes (logand (+ (max (* fifo-dwords 4) 256) 127) - (lognot 127))) - (period-count 3) - (buf-len (* period-bytes period-count)) - (n-samples (truncate period-bytes 2)) ; bytes to 16-bit samples - (float-sample-buffer (make-array n-samples :element-type 'single-float)) - (buffer (hda-dma-buffer-phys hda)) - (buffer-offset 0) - (stop-countdown nil)) - (mezzano.supervisor:debug-print-line - "HDA FIFO " raw-fifo-dwords " DWords (capped to " fifo-dwords - "), period " period-bytes " B × " period-count " = " buf-len " B total") - (start-playback hda buffer buf-len - (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) + (labels ((store-sample (sample offset) + ;; Clamp to limits, don't wrap. + (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) + (sample-rescaled (if (< sample-clamped 0.0f0) + (* sample-clamped 32768.0f0) + (* sample-clamped 32767.0f0))) + (sample-16bit (truncate sample-rescaled))) + (declare (optimize speed (safety 0)) + (type single-float sample-clamped sample-rescaled) + (type fixnum sample-16bit)) + (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) + (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) + (refill-fifo () + (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) + (setf stop-countdown nil)) + ((not stop-countdown) + (setf stop-countdown 2048))) + (with-hda-access (hda) + (locally + (declare (optimize speed (safety 0)) + (type (simple-array single-float (*)) float-sample-buffer) + (type fixnum n-samples)) + (dotimes (i n-samples) + (store-sample (aref float-sample-buffer i) i)))) + (setf buffer-offset (rem (+ buffer-offset period-bytes) + buf-len)))) + ;; Zero the DMA buffer so stale data from previous runs doesn't play. + (dotimes (i buf-len) + (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) + ;; Prepopulate the initial buffer. + (dotimes (i period-count) + (refill-fifo)) + (with-hda-access (hda) + ;; Unmask the interrupt, flush any pending IRQs. + (clear-pending-interrupt hda output-stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) + ;; Begin playback. + (multiple-value-bind (converter mixer) + (output-path output-pin) + (unless converter + (error "HDA output pin ~D has no converter." (nid output-pin))) + (log-selected-output-path output-pin converter mixer) + (set-widget-power-state-d0 output-pin) + (set-widget-power-state-d0 converter) + (when mixer + (set-widget-power-state-d0 mixer)) + (maybe-enable-eapd output-pin) + (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) :period-bytes period-bytes - :period-count period-count) - (labels ((store-sample (sample offset) - ;; Clamp to limits, don't wrap. - (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) - (sample-rescaled (if (< sample-clamped 0.0f0) - (* sample-clamped 32768.0f0) - (* sample-clamped 32767.0f0))) - (sample-16bit (truncate sample-rescaled))) - (declare (optimize speed (safety 0)) - (type single-float sample-clamped sample-rescaled) - (type fixnum sample-16bit)) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) - (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) - (refill-fifo () - (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) - (setf stop-countdown nil)) - ((not stop-countdown) - (setf stop-countdown 4))) - (with-hda-access (hda) - (locally - (declare (optimize speed (safety 0)) - (type (simple-array single-float (*)) float-sample-buffer) - (type fixnum n-samples)) - (dotimes (i n-samples) - (store-sample (aref float-sample-buffer i) i)))) - (setf buffer-offset (rem (+ buffer-offset period-bytes) - buf-len)))) - ;; Prepopulate the initial buffer. - (dotimes (i period-count) - (refill-fifo)) - (with-hda-access (hda) - ;; Unmask the interrupt, flush any pending IRQs. - (clear-pending-interrupt hda output-stream) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) - (unwind-protect - (loop - ;; Wait for the dma position to move from the - ;; current period to the next period. - (let* ((dmap (dma-position hda output-stream)) - (lpib (sd-reg/32 hda output-stream +sdnlpib+)) - (sts (sd-reg/32 hda output-stream +sdnctlsts+)) - (current-offset (truncate dmap period-bytes))) - (when (and (zerop dmap) - (zerop lpib) - (not (logtest sts (ash 1 1)))) - (mezzano.supervisor:debug-print-line - "HDA stream stalled: dmap=" dmap - " lpib=" lpib - " ctlsts=#x" sts - " intsts=#x" (global-reg/32 hda +intsts+))) - (when (not (eql current-offset (truncate buffer-offset period-bytes))) - (when stop-countdown - (when (zerop stop-countdown) - (return)) - (decf stop-countdown)) - ;; Refill buffer. - (refill-fifo))) - (wait-for-buffer-interrupt hda)) - (with-hda-access (hda) - (stream-reset hda (first-output-stream hda)) - (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))))) + :period-count period-count)) + (unwind-protect + (loop + ;; Wait for the dma position to move from the + ;; current period to the next period. + (let* ((dmap (dma-position hda output-stream)) + (lpib (sd-reg/32 hda output-stream +sdnlpib+)) + (sts (sd-reg/32 hda output-stream +sdnctlsts+)) + (current-offset (truncate dmap period-bytes))) + ;; Underrun detection — increment period on FIFO or descriptor error. + (when (logtest sts (logior (ash 1 27) (ash 1 28))) + (let* ((step 64) + (new-bytes (min (+ period-bytes step) 4096))) + (mezzano.supervisor:debug-print-line + "HDA underrun, period " period-bytes " → " new-bytes) + (setf period-bytes new-bytes + buf-len (* period-bytes period-count) + n-samples (truncate period-bytes 2) + float-sample-buffer (make-array n-samples :element-type 'single-float) + buffer-offset 0) + (with-hda-access (hda) + (stream-reset hda output-stream) + (dotimes (i period-count) + (write-bdl hda i + (+ buffer (* i period-bytes)) + period-bytes)) + (prep-stream hda output-stream 0 period-count buf-len) + (dotimes (i period-count) + (refill-fifo)) + (clear-pending-interrupt hda output-stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (stream-go hda output-stream)))) + (when (and (zerop dmap) + (zerop lpib) + (not (logtest sts (ash 1 1)))) + (mezzano.supervisor:debug-print-line + "HDA stream stalled: dmap=" dmap + " lpib=" lpib + " ctlsts=#x" sts + " intsts=#x" (global-reg/32 hda +intsts+))) + (when (not (eql current-offset (truncate buffer-offset period-bytes))) + (when stop-countdown + (when (zerop stop-countdown) + (return)) + (decf stop-countdown)) + ;; Refill buffer. + (refill-fifo))) + (wait-for-buffer-interrupt hda)) + (with-hda-access (hda) + (stream-reset hda (first-output-stream hda)) + (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) (device-disconnect () (format t "HDA ~S disconnected.~%" hda) (throw 'mezzano.supervisor:terminate-thread nil)))) From 7506574e7ae4e1b8f2a8cbb1c470aa29ff10056c Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Thu, 14 May 2026 03:20:50 +0500 Subject: [PATCH 05/15] sound: try-lock in audio callback, fix output-sound assert --- drivers/sound.lisp | 53 +++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/drivers/sound.lisp b/drivers/sound.lisp index 5dfc611dc..0d8dd54ad 100644 --- a/drivers/sound.lisp +++ b/drivers/sound.lisp @@ -131,25 +131,28 @@ (type fixnum start end)) (fill buffer 0.0 :start start :end end)) (prog1 - (mezzano.supervisor:with-mutex (*sink-lock*) - (cond ((endp *sinks*) - ;; No more sinks, stop the card. - nil) - (t - ;; Fill buffer from sinks as much as possible. - (dolist (sink *sinks*) - (mix-out-of-sink sink buffer start end)) - ;; Remove all empty sinks. - (setf *sinks* (delete-if - (lambda (sink) - (buffer-empty sink)) - *sinks*)) - (when (endp *sinks*) - (setf (sup:event-state *sinks-present-event*) nil)) - ;; Samples have been consumed, wake fillers. - (mezzano.supervisor:condition-notify *sink-cvar* t) - ;; Leave the card running. - t))))) + ;; Try-lock the sink mutex. If the music player holds it (inside + ;; output-sound's transcode path), return T immediately — the + ;; buffer is already zeroed, so the HDA plays silence for this + ;; period. Next period will retry. + (if (mezzano.supervisor:acquire-mutex *sink-lock* nil) + (let ((result + (cond ((endp *sinks*) + nil) + (t + (dolist (sink *sinks*) + (mix-out-of-sink sink buffer start end)) + (setf *sinks* (delete-if + (lambda (sink) + (buffer-empty sink)) + *sinks*)) + (when (endp *sinks*) + (setf (sup:event-state *sinks-present-event*) nil)) + (mezzano.supervisor:condition-notify *sink-cvar* t) + t)))) + (mezzano.supervisor:release-mutex *sink-lock*) + result) + t))) (defun sound-worker (device) (loop @@ -307,11 +310,13 @@ (mezzano.supervisor:with-mutex (*sink-lock*) (let ((offset start)) (loop - (when (buffer-empty sink) - ;; Buffer is currently empty, sink is not live. - (assert (not (member sink *sinks*))) - (setf (sup:event-state *sinks-present-event*) t) - (push sink *sinks*)) + (when (buffer-empty sink) + ;; Buffer is currently empty, sink is not live. + ;; Only add to the sink list if not already present — the + ;; callback may have left it if it was re-filled concurrently. + (unless (member sink *sinks*) + (setf (sup:event-state *sinks-present-event*) t) + (push sink *sinks*))) ;; Fill the buffer as much as possible. (multiple-value-bind (total-samples-copied total-elements-consumed) From 5c3ba3e86a1f0e5dd20896270f63f126fd186312 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Thu, 14 May 2026 03:20:55 +0500 Subject: [PATCH 06/15] music-player: reader thread for glitch-free playback --- gui/music-player.lisp | 65 ++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/gui/music-player.lisp b/gui/music-player.lisp index 409945e15..8ac013feb 100644 --- a/gui/music-player.lisp +++ b/gui/music-player.lisp @@ -53,8 +53,7 @@ (defun worker (player) (unwind-protect - (let* ((data-buffer (make-array #x200 :element-type '(unsigned-byte 8))) - (stream (player-stream player)) + (let* ((stream (player-stream player)) (riff-chunks (wav:read-wav-file stream :chunk-data-reader (wrap-data-chunk-data-samples-reader))) (riff (find "RIFF" riff-chunks @@ -85,26 +84,48 @@ (member sample-size '(8 16)))) (format t "Unsupported wav file. ~S ~S ~S ~S ~S~%" fmt compression channels sample-rate sample-size) (return-from worker)) - (file-position stream data-file-position) - (let ((audio-sink (mezzano.driver.sound:make-sound-output-sink - :buffer-duration 0.05 - :format (ecase sample-size - (8 :pcm-u8) - (16 :pcm-s16le)))) - (current-position 0)) - (unwind-protect - (loop - (when (>= current-position data-size) - (format t "Reached EOF.~%") - (return)) - (let ((n-bytes (min (length data-buffer) - (- data-size current-position)))) - (read-sequence data-buffer stream :end n-bytes) - (mezzano.driver.sound:output-sound data-buffer audio-sink :end n-bytes) - (incf current-position n-bytes)))) - (mezzano.driver.sound:flush-sink audio-sink)))) - (mezzano.supervisor:fifo-push (make-instance 'worker-exited) - (fifo player)))) + (file-position stream data-file-position) + (let* ((chunk-size #x4000) + (audio-sink (mezzano.driver.sound:make-sound-output-sink + :buffer-duration 0.01 + :format (ecase sample-size + (8 :pcm-u8) + (16 :pcm-s16le)))) + (current-position 0) + (chunk-fifo (mezzano.supervisor:make-fifo 4))) + ;; Pre-fill: push first chunks so the sink has data + ;; immediately when the sound worker starts playback. + (dotimes (i 4) + (when (>= current-position data-size) (return)) + (let* ((n-bytes (min chunk-size (- data-size current-position))) + (buf (make-array n-bytes :element-type '(unsigned-byte 8)))) + (read-sequence buf stream) + (mezzano.driver.sound:output-sound buf audio-sink :end n-bytes) + (incf current-position n-bytes))) + ;; Reader thread: reads remaining chunks into FIFO while + ;; the main thread pushes them to the sink. Reading and + ;; playback overlap, so a slow read never drains the sink. + (mezzano.supervisor:make-thread + (lambda () + (unwind-protect + (loop + (when (>= current-position data-size) (return)) + (let* ((n-bytes (min chunk-size (- data-size current-position))) + (buf (make-array n-bytes :element-type '(unsigned-byte 8)))) + (read-sequence buf stream) + (incf current-position n-bytes) + (mezzano.supervisor:fifo-push buf chunk-fifo))) + (mezzano.supervisor:fifo-push nil chunk-fifo))) + :name (format nil "Audio reader for ~S" player)) + (unwind-protect + (loop + (let ((buf (mezzano.supervisor:fifo-pop chunk-fifo))) + (when (null buf) (return)) + (mezzano.driver.sound:output-sound buf audio-sink + :end (length buf)))) + (mezzano.driver.sound:flush-sink audio-sink))))) + (mezzano.supervisor:fifo-push (make-instance 'worker-exited) + (fifo player)))) (defun main (path) (with-simple-restart (abort "Close music player") From 485c1bf356189056a9304fb39adc3c7ea5fd634a Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Thu, 14 May 2026 03:51:42 +0500 Subject: [PATCH 07/15] intel-hda: oops forgot --- drivers/intel-hda.lisp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index dc16c4c44..d644501cd 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -199,7 +199,6 @@ (defconstant +dmap-entry-size+ 4) (defconstant +bdl-entry-size+ 16) (defconstant +max-bdl-entries+ 256) -(defconstant +playback-period-bytes+ #x0100) (defconstant +playback-period-count+ 3) (defparameter *hda-min-period-bytes* 512 @@ -228,7 +227,8 @@ (irq :initarg :irq :accessor hda-irq) (dma-buffer-phys :initarg :dma-buffer-phys :accessor hda-dma-buffer-phys) (dma-buffer-virt :initarg :dma-buffer-virt :accessor hda-dma-buffer-virt) - (dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size)) + (dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size) + (period-bytes :initarg :period-bytes :accessor hda-period-bytes)) (:default-initargs :codecs (make-array 15 :initial-element nil))) (define-condition device-disconnect () ()) @@ -965,10 +965,19 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (with-hda-access (hda) (gcap-oss (global-reg/16 hda +gcap+)))) +(defun compute-period-bytes (hda stream-id) + (with-hda-access (hda) + (let* ((fifo-size (sd-reg/16 hda stream-id +sdnfifos+)) + (fifo-aligned (* (ceiling fifo-size 128) 128)) + (period-bytes (max fifo-aligned *hda-min-period-bytes*))) + (mezzano.supervisor:debug-print-line + "HDA stream " stream-id " FIFO " fifo-size " period " period-bytes) + period-bytes))) + (defun start-playback (hda buffer buffer-size codec dac pin &optional mixer &key - (period-bytes +playback-period-bytes+) - (period-count +playback-period-count+)) + (period-bytes (compute-period-bytes hda (first-output-stream hda))) + (period-count +playback-period-count+)) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) (dotimes (i period-count) @@ -1088,15 +1097,16 @@ Returns NIL if there is no output path." (defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback) (handler-case (let* ((buffer (hda-dma-buffer-phys hda)) - (period-bytes (max +playback-period-bytes+ *hda-min-period-bytes*)) + (output-stream (first-output-stream hda)) + (period-bytes (compute-period-bytes hda output-stream)) (period-count +playback-period-count+) (buf-len (* period-bytes period-count)) (n-samples (truncate period-bytes 2)) ; bytes to stereo 16-bit samples (float-sample-buffer (make-array n-samples :element-type 'single-float)) (output-pin (default-output-pin hda)) - (output-stream (first-output-stream hda)) (buffer-offset 0) (stop-countdown nil)) + (setf (hda-period-bytes hda) period-bytes) (unless output-pin (error "No HDA output pin with a playback path found.")) (labels ((store-sample (sample offset) @@ -1160,10 +1170,11 @@ Returns NIL if there is no output path." ;; Underrun detection — increment period on FIFO or descriptor error. (when (logtest sts (logior (ash 1 27) (ash 1 28))) (let* ((step 64) - (new-bytes (min (+ period-bytes step) 4096))) + (new-bytes (max (min (+ period-bytes step) 4096) *hda-min-period-bytes*))) (mezzano.supervisor:debug-print-line "HDA underrun, period " period-bytes " → " new-bytes) (setf period-bytes new-bytes + (hda-period-bytes hda) period-bytes buf-len (* period-bytes period-count) n-samples (truncate period-bytes 2) float-sample-buffer (make-array n-samples :element-type 'single-float) From d9200cb2cf28f2168d4c528b248f597ec61193a3 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Thu, 14 May 2026 05:31:29 +0500 Subject: [PATCH 08/15] intel-hda: off-by-one --- drivers/intel-hda.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index d644501cd..6fff0f1ff 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -967,7 +967,7 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (defun compute-period-bytes (hda stream-id) (with-hda-access (hda) - (let* ((fifo-size (sd-reg/16 hda stream-id +sdnfifos+)) + (let* ((fifo-size (1+ (sd-reg/16 hda stream-id +sdnfifos+))) (fifo-aligned (* (ceiling fifo-size 128) 128)) (period-bytes (max fifo-aligned *hda-min-period-bytes*))) (mezzano.supervisor:debug-print-line From 77b6ac04dde8fe9d4ab86b750bc19096f73a0ec3 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Thu, 14 May 2026 14:47:31 +0500 Subject: [PATCH 09/15] intel-hda: fixes --- drivers/intel-hda.lisp | 125 ++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 71 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index 6fff0f1ff..6696c4b90 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -848,6 +848,9 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (hda-irq hda) (mezzano.supervisor:make-simple-irq (pci:pci-intr-line device))) (format t "Found Intel HDA controller at ~S.~%" device) (setf (pci:pci-bus-master-enabled device) t) + ;; Clear TCSEL PCI register bits 0-2 to fix playback static on ICH. + (let ((tcel (pci:pci-config/8 device #x44))) + (setf (pci:pci-config/8 device #x44) (logand tcel #b11111000))) ;; Perform a controller reset by pulsing crst to 0. (format t "Begin reset.~%") (setf (global-reg/32 hda +gctl+) 0) @@ -938,7 +941,9 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (sd-reg/16 hda stream-id +sdnfmt+) #x4011 ;; LVI is the last valid descriptor index, not the descriptor count. (sd-reg/16 hda stream-id +sdnlvi+) (1- bdl-length) - (sd-reg/32 hda stream-id +sdncbl+) cb-length))) + (sd-reg/32 hda stream-id +sdncbl+) cb-length) + ;; Poll FIFO size until non-zero (FIFO ready after format is set). + (loop while (zerop (sd-reg/16 hda stream-id +sdnfifos+))))) (defun stream-reset (hda stream-id) ;; Clear the run bit before doing anything. @@ -954,8 +959,13 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (setf (global-reg/32 hda +intctl+) 0)) (defun stream-go (hda stream-id) - ;; run, IoC enabled, stream number (tag) 1. - (setf (sd-reg/32 hda stream-id +sdnctlsts+) #x00100006)) + (setf (sd-reg/32 hda stream-id +sdnctlsts+) + (logior #x00100000 ; STRM = 1 (matches codec verb #x70610) + (ash 1 18) ; TP (traffic priority) + (ash 1 4) ; DEIE + (ash 1 3) ; FEIE + (ash 1 2) ; IOCE + (ash 1 1)))) ; RUN (defun first-input-stream (hda) (declare (ignore hda)) @@ -980,20 +990,18 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (period-count +playback-period-count+)) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) + (clear-pending-interrupt hda (first-output-stream hda)) (dotimes (i period-count) (write-bdl hda i (+ buffer (* i period-bytes)) period-bytes)) (prep-stream hda (first-output-stream hda) 0 period-count buffer-size) - (when mixer - (command hda codec mixer #x3F07F)) ; unmute L/R out/in (command hda codec dac #x70610) ; stream=1 (command hda codec dac #x24011) ; format - (command hda codec dac #x3b07f) ; unmute L/R out - (command hda codec pin #x3b07f) ; unmute L/R out (command hda codec pin #x70740) ; enable output ;; Enable global and stream interrupts. - (setf (global-reg/32 hda +intctl+) (logior #xC0000000 (ash 1 (first-output-stream hda)))) + (setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda)))) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) @@ -1104,8 +1112,12 @@ Returns NIL if there is no output path." (n-samples (truncate period-bytes 2)) ; bytes to stereo 16-bit samples (float-sample-buffer (make-array n-samples :element-type 'single-float)) (output-pin (default-output-pin hda)) - (buffer-offset 0) - (stop-countdown nil)) + (buffer-offset 0) + (stop-countdown nil) + (fifo-size 0) + (needs-unmute t) + (dac-nid 0) + (mixer-nid nil)) (setf (hda-period-bytes hda) period-bytes) (unless output-pin (error "No HDA output pin with a playback path found.")) @@ -1135,16 +1147,12 @@ Returns NIL if there is no output path." (store-sample (aref float-sample-buffer i) i)))) (setf buffer-offset (rem (+ buffer-offset period-bytes) buf-len)))) - ;; Zero the DMA buffer so stale data from previous runs doesn't play. - (dotimes (i buf-len) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) - ;; Prepopulate the initial buffer. - (dotimes (i period-count) - (refill-fifo)) - (with-hda-access (hda) - ;; Unmask the interrupt, flush any pending IRQs. - (clear-pending-interrupt hda output-stream) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda))) + ;; Zero the DMA buffer so stale data from previous runs doesn't play. + (mezzano.supervisor::zeroize-physical-page buffer) + (with-hda-access (hda) + (clear-pending-interrupt hda output-stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (setf fifo-size (1+ (sd-reg/16 hda output-stream +sdnfifos+)))) ;; Begin playback. (multiple-value-bind (converter mixer) (output-path output-pin) @@ -1159,57 +1167,32 @@ Returns NIL if there is no output path." (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) :period-bytes period-bytes :period-count period-count)) - (unwind-protect - (loop - ;; Wait for the dma position to move from the - ;; current period to the next period. - (let* ((dmap (dma-position hda output-stream)) - (lpib (sd-reg/32 hda output-stream +sdnlpib+)) - (sts (sd-reg/32 hda output-stream +sdnctlsts+)) - (current-offset (truncate dmap period-bytes))) - ;; Underrun detection — increment period on FIFO or descriptor error. - (when (logtest sts (logior (ash 1 27) (ash 1 28))) - (let* ((step 64) - (new-bytes (max (min (+ period-bytes step) 4096) *hda-min-period-bytes*))) - (mezzano.supervisor:debug-print-line - "HDA underrun, period " period-bytes " → " new-bytes) - (setf period-bytes new-bytes - (hda-period-bytes hda) period-bytes - buf-len (* period-bytes period-count) - n-samples (truncate period-bytes 2) - float-sample-buffer (make-array n-samples :element-type 'single-float) - buffer-offset 0) - (with-hda-access (hda) - (stream-reset hda output-stream) - (dotimes (i period-count) - (write-bdl hda i - (+ buffer (* i period-bytes)) - period-bytes)) - (prep-stream hda output-stream 0 period-count buf-len) - (dotimes (i period-count) - (refill-fifo)) - (clear-pending-interrupt hda output-stream) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) - (stream-go hda output-stream)))) - (when (and (zerop dmap) - (zerop lpib) - (not (logtest sts (ash 1 1)))) - (mezzano.supervisor:debug-print-line - "HDA stream stalled: dmap=" dmap - " lpib=" lpib - " ctlsts=#x" sts - " intsts=#x" (global-reg/32 hda +intsts+))) - (when (not (eql current-offset (truncate buffer-offset period-bytes))) - (when stop-countdown - (when (zerop stop-countdown) - (return)) - (decf stop-countdown)) - ;; Refill buffer. - (refill-fifo))) - (wait-for-buffer-interrupt hda)) - (with-hda-access (hda) - (stream-reset hda (first-output-stream hda)) - (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) + (mezzano.supervisor:debug-print-line + "HDA entering loop, lpib=" (sd-reg/32 hda output-stream +sdnlpib+)) + (unwind-protect + (loop + (wait-for-buffer-interrupt hda) + (let* ((lpib (sd-reg/32 hda output-stream +sdnlpib+)) + (lpib-fifo (mod (+ lpib fifo-size) buf-len)) + (current-offset (truncate lpib-fifo period-bytes))) + (when (not (eql current-offset (truncate buffer-offset period-bytes))) + (when stop-countdown + (when (zerop stop-countdown) (return)) + (decf stop-countdown)) + (refill-fifo) + (when needs-unmute + (setf needs-unmute nil) + (mezzano.supervisor:debug-print-line + "HDA first refill, lpib=" lpib " lpib-fifo=" lpib-fifo + " buf-offset=" buffer-offset " period=" period-bytes) + (with-hda-access (hda) + (command hda (cad output-pin) dac-nid #x3b07f) ; unmute DAC + (command hda (cad output-pin) (nid output-pin) #x3b07f) ; unmute pin + (when mixer-nid + (command hda (cad output-pin) mixer-nid #x3F07F))))))) + (with-hda-access (hda) + (stream-reset hda (first-output-stream hda)) + (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) (device-disconnect () (format t "HDA ~S disconnected.~%" hda) (throw 'mezzano.supervisor:terminate-thread nil)))) From 4a2f110055d27e2e58e75d42c48fa2fc3fc73c13 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Fri, 15 May 2026 01:03:45 +0500 Subject: [PATCH 10/15] intel-hda: rah --- drivers/intel-hda.lisp | 109 ++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index 6696c4b90..6a500a780 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -996,13 +996,18 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (+ buffer (* i period-bytes)) period-bytes)) (prep-stream hda (first-output-stream hda) 0 period-count buffer-size) + (when mixer + (command hda codec mixer #x3F07F)) ; unmute L/R out/in (command hda codec dac #x70610) ; stream=1 - (command hda codec dac #x24011) ; format - (command hda codec pin #x70740) ; enable output - ;; Enable global and stream interrupts. - (setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda)))) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) - (stream-go hda (first-output-stream hda)))) + (command hda codec dac #x24011) ; format + (command hda codec dac #x3b07f) ; unmute L/R out + (command hda codec pin #x3b07f) ; unmute L/R out + (command hda codec pin #x70740) ; enable output + ;; Enable global and stream interrupts. + (setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda)))) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (sys.int::dma-write-barrier) + (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) ;; Clear any latched stream status in the status byte. qemu is picky and @@ -1109,20 +1114,16 @@ Returns NIL if there is no output path." (period-bytes (compute-period-bytes hda output-stream)) (period-count +playback-period-count+) (buf-len (* period-bytes period-count)) - (n-samples (truncate period-bytes 2)) ; bytes to stereo 16-bit samples + (n-samples (truncate period-bytes 2)) (float-sample-buffer (make-array n-samples :element-type 'single-float)) (output-pin (default-output-pin hda)) - (buffer-offset 0) - (stop-countdown nil) - (fifo-size 0) - (needs-unmute t) - (dac-nid 0) - (mixer-nid nil)) + (buffer-offset 0) + (stop-countdown nil) + (fifo-size 0)) (setf (hda-period-bytes hda) period-bytes) (unless output-pin (error "No HDA output pin with a playback path found.")) (labels ((store-sample (sample offset) - ;; Clamp to limits, don't wrap. (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) (sample-rescaled (if (< sample-clamped 0.0f0) (* sample-clamped 32768.0f0) @@ -1137,22 +1138,26 @@ Returns NIL if there is no output path." (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) (setf stop-countdown nil)) ((not stop-countdown) - (setf stop-countdown 2048))) - (with-hda-access (hda) - (locally - (declare (optimize speed (safety 0)) - (type (simple-array single-float (*)) float-sample-buffer) - (type fixnum n-samples)) - (dotimes (i n-samples) - (store-sample (aref float-sample-buffer i) i)))) - (setf buffer-offset (rem (+ buffer-offset period-bytes) - buf-len)))) - ;; Zero the DMA buffer so stale data from previous runs doesn't play. - (mezzano.supervisor::zeroize-physical-page buffer) - (with-hda-access (hda) - (clear-pending-interrupt hda output-stream) - (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) - (setf fifo-size (1+ (sd-reg/16 hda output-stream +sdnfifos+)))) + (setf stop-countdown (* 4 period-count)))) + (with-hda-access (hda) + (locally + (declare (optimize speed (safety 0)) + (type (simple-array single-float (*)) float-sample-buffer) + (type fixnum n-samples)) + (dotimes (i n-samples) + (store-sample (aref float-sample-buffer i) i)))) + (sys.int::dma-write-barrier) + (setf buffer-offset (rem (+ buffer-offset period-bytes) buf-len)))) + ;; Zero the used DMA buffer area. + (dotimes (i buf-len) + (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) + (with-hda-access (hda) + (clear-pending-interrupt hda output-stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (setf fifo-size (1+ (sd-reg/16 hda output-stream +sdnfifos+)))) + ;; Pre-fill all periods before starting DMA. + (dotimes (i period-count) + (refill-fifo)) ;; Begin playback. (multiple-value-bind (converter mixer) (output-path output-pin) @@ -1167,32 +1172,24 @@ Returns NIL if there is no output path." (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) :period-bytes period-bytes :period-count period-count)) - (mezzano.supervisor:debug-print-line - "HDA entering loop, lpib=" (sd-reg/32 hda output-stream +sdnlpib+)) - (unwind-protect - (loop - (wait-for-buffer-interrupt hda) - (let* ((lpib (sd-reg/32 hda output-stream +sdnlpib+)) - (lpib-fifo (mod (+ lpib fifo-size) buf-len)) - (current-offset (truncate lpib-fifo period-bytes))) - (when (not (eql current-offset (truncate buffer-offset period-bytes))) - (when stop-countdown - (when (zerop stop-countdown) (return)) - (decf stop-countdown)) - (refill-fifo) - (when needs-unmute - (setf needs-unmute nil) - (mezzano.supervisor:debug-print-line - "HDA first refill, lpib=" lpib " lpib-fifo=" lpib-fifo - " buf-offset=" buffer-offset " period=" period-bytes) - (with-hda-access (hda) - (command hda (cad output-pin) dac-nid #x3b07f) ; unmute DAC - (command hda (cad output-pin) (nid output-pin) #x3b07f) ; unmute pin - (when mixer-nid - (command hda (cad output-pin) mixer-nid #x3F07F))))))) - (with-hda-access (hda) - (stream-reset hda (first-output-stream hda)) - (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) + (mezzano.supervisor:debug-print-line + "HDA entering loop, lpib=" (sd-reg/32 hda output-stream +sdnlpib+)) + (unwind-protect + (loop + (ecase (wait-for-buffer-interrupt hda) + (:timeout) + (:interrupt + (let* ((lpib (sd-reg/32 hda output-stream +sdnlpib+)) + (lpib-fifo (mod (+ lpib fifo-size) buf-len)) + (current-offset (truncate lpib-fifo period-bytes))) + (when (not (eql current-offset (truncate buffer-offset period-bytes))) + (when stop-countdown + (when (zerop stop-countdown) (return)) + (decf stop-countdown)) + (refill-fifo)))))) + (with-hda-access (hda) + (stream-reset hda (first-output-stream hda)) + (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) (device-disconnect () (format t "HDA ~S disconnected.~%" hda) (throw 'mezzano.supervisor:terminate-thread nil)))) From e7a17013777b138a99b4ee8d239a0af2948e495c Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Fri, 15 May 2026 01:41:17 +0500 Subject: [PATCH 11/15] intel-hde: no more garbage --- drivers/intel-hda.lisp | 73 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index 6a500a780..319e7d4f6 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -206,6 +206,15 @@ glitching at the cost of higher latency. Must be a multiple of 128. Default: 256 bytes = 64 stereo frames = ~1.45ms at 44100Hz.") +(defparameter *i-want-garbage* nil + "When true, skip the startup silence delay. The first ~100ms of audio + may be garbled while the host audio backend initializes.") + +(defparameter *hda-startup-periods* 90 + "Number of periods to run silently at startup before unmuting. + The DMA buffer is pre-filled and the SRC adapts during this window. + Total startup silence = *hda-startup-periods* × (period-bytes / 176400) seconds.") + (defconstant +corb-offset+ 0) (defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+)) (defconstant +dmap-offset+ (+ +rirb-offset+ +rirb-max-size+)) @@ -987,7 +996,9 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (defun start-playback (hda buffer buffer-size codec dac pin &optional mixer &key (period-bytes (compute-period-bytes hda (first-output-stream hda))) - (period-count +playback-period-count+)) + (period-count +playback-period-count+) + (startup-delay-seconds 0) + (mute-for-startup nil)) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) (clear-pending-interrupt hda (first-output-stream hda)) @@ -996,17 +1007,25 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (+ buffer (* i period-bytes)) period-bytes)) (prep-stream hda (first-output-stream hda) 0 period-count buffer-size) - (when mixer - (command hda codec mixer #x3F07F)) ; unmute L/R out/in (command hda codec dac #x70610) ; stream=1 (command hda codec dac #x24011) ; format - (command hda codec dac #x3b07f) ; unmute L/R out - (command hda codec pin #x3b07f) ; unmute L/R out - (command hda codec pin #x70740) ; enable output + (when mixer + (command hda codec mixer #x3F07F)) ; unmute L/R out/in + (if mute-for-startup + (progn + ;; Mute the DAC output so the SRC can adapt silently while + ;; the host audio backend initializes. + (command hda codec dac #x3b0ff)) ; mute + (progn + (command hda codec dac #x3b07f) ; unmute L/R out + (command hda codec pin #x3b07f) ; unmute L/R out + (command hda codec pin #x70740))) ; enable output ;; Enable global and stream interrupts. (setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda)))) (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) (sys.int::dma-write-barrier) + (when (plusp startup-delay-seconds) + (sleep startup-delay-seconds)) (stream-go hda (first-output-stream hda)))) (defun clear-pending-interrupt (hda stream) @@ -1119,7 +1138,15 @@ Returns NIL if there is no output path." (output-pin (default-output-pin hda)) (buffer-offset 0) (stop-countdown nil) - (fifo-size 0)) + (fifo-size 0) + (mute-startup (not *i-want-garbage*)) + (startup-delay (if *i-want-garbage* 0 *hda-startup-periods*)) + (startup-delay-seconds (if *i-want-garbage* 0.0 + (/ (* period-bytes *hda-startup-periods*) + 176400.0))) + (startup-dac-nid nil) + (startup-pin-nid nil) + (startup-cad nil)) (setf (hda-period-bytes hda) period-bytes) (unless output-pin (error "No HDA output pin with a playback path found.")) @@ -1135,7 +1162,11 @@ Returns NIL if there is no output path." (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) (refill-fifo () - (cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) + (cond ((plusp startup-delay) + ;; Still in muted startup: fill with silence, don't + ;; consume real audio from the sink. + (fill float-sample-buffer 0.0)) + ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) (setf stop-countdown nil)) ((not stop-countdown) (setf stop-countdown (* 4 period-count)))) @@ -1155,9 +1186,10 @@ Returns NIL if there is no output path." (clear-pending-interrupt hda output-stream) (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) (setf fifo-size (1+ (sd-reg/16 hda output-stream +sdnfifos+)))) - ;; Pre-fill all periods before starting DMA. - (dotimes (i period-count) - (refill-fifo)) + ;; Pre-fill all periods before starting DMA. + (unless mute-startup + (dotimes (i period-count) + (refill-fifo))) ;; Begin playback. (multiple-value-bind (converter mixer) (output-path output-pin) @@ -1169,9 +1201,15 @@ Returns NIL if there is no output path." (when mixer (set-widget-power-state-d0 mixer)) (maybe-enable-eapd output-pin) + (when mute-startup + (setf startup-dac-nid (nid converter) + startup-pin-nid (nid output-pin) + startup-cad (cad output-pin))) (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) :period-bytes period-bytes - :period-count period-count)) + :period-count period-count + :startup-delay-seconds startup-delay-seconds + :mute-for-startup mute-startup)) (mezzano.supervisor:debug-print-line "HDA entering loop, lpib=" (sd-reg/32 hda output-stream +sdnlpib+)) (unwind-protect @@ -1186,7 +1224,16 @@ Returns NIL if there is no output path." (when stop-countdown (when (zerop stop-countdown) (return)) (decf stop-countdown)) - (refill-fifo)))))) + (refill-fifo) + (when (plusp startup-delay) + (decf startup-delay) + (when (zerop startup-delay) + (mezzano.supervisor:debug-print-line + "HDA startup complete, unmuting") + (with-hda-access (hda) + (command hda startup-cad startup-dac-nid #x3b07f) + (command hda startup-cad startup-pin-nid #x3b07f) + (command hda startup-cad startup-pin-nid #x70740))))))))) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) From 7d21f801f93b4f22dd26c8687158757b19288309 Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Fri, 15 May 2026 01:49:36 +0500 Subject: [PATCH 12/15] intel-hda: keepalive parameter --- drivers/intel-hda.lisp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index 319e7d4f6..fd0d73414 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -215,6 +215,11 @@ The DMA buffer is pre-filled and the SRC adapts during this window. Total startup silence = *hda-startup-periods* × (period-bytes / 176400) seconds.") +(defparameter *hda-idle-periods* 12 + "Number of silent periods after the sink drains before stopping the stream. + Higher values keep the HDA initialized longer, avoiding startup delays on + the next playback at the cost of keeping the IRQ active.") + (defconstant +corb-offset+ 0) (defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+)) (defconstant +dmap-offset+ (+ +rirb-offset+ +rirb-max-size+)) @@ -1169,7 +1174,7 @@ Returns NIL if there is no output path." ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) (setf stop-countdown nil)) ((not stop-countdown) - (setf stop-countdown (* 4 period-count)))) + (setf stop-countdown *hda-idle-periods*))) (with-hda-access (hda) (locally (declare (optimize speed (safety 0)) From 7ff20a35b17e6178af3d08f491bf61495f5ab5ac Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Fri, 15 May 2026 03:33:32 +0500 Subject: [PATCH 13/15] intel-hda: buffer recovery + rewrite --- drivers/intel-hda.lisp | 240 ++++++++++++++++++++++------------------- 1 file changed, 131 insertions(+), 109 deletions(-) diff --git a/drivers/intel-hda.lisp b/drivers/intel-hda.lisp index fd0d73414..8f56f9e8b 100644 --- a/drivers/intel-hda.lisp +++ b/drivers/intel-hda.lisp @@ -201,10 +201,8 @@ (defconstant +max-bdl-entries+ 256) (defconstant +playback-period-count+ 3) -(defparameter *hda-min-period-bytes* 512 - "Minimum size of each HDA audio period in bytes. Larger values reduce - glitching at the cost of higher latency. Must be a multiple of 128. - Default: 256 bytes = 64 stereo frames = ~1.45ms at 44100Hz.") +(defparameter *hda-min-period-bytes* 256 + "Minimum size of each HDA audio period in bytes. Must be a multiple of 128.") (defparameter *i-want-garbage* nil "When true, skip the startup silence delay. The first ~100ms of audio @@ -242,7 +240,8 @@ (dma-buffer-phys :initarg :dma-buffer-phys :accessor hda-dma-buffer-phys) (dma-buffer-virt :initarg :dma-buffer-virt :accessor hda-dma-buffer-virt) (dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size) - (period-bytes :initarg :period-bytes :accessor hda-period-bytes)) + (period-bytes :initarg :period-bytes :accessor hda-period-bytes) + (stable-period-bytes :initform nil :accessor hda-stable-period-bytes)) (:default-initargs :codecs (make-array 15 :initial-element nil))) (define-condition device-disconnect () ()) @@ -991,12 +990,13 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED.")) (defun compute-period-bytes (hda stream-id) (with-hda-access (hda) - (let* ((fifo-size (1+ (sd-reg/16 hda stream-id +sdnfifos+))) - (fifo-aligned (* (ceiling fifo-size 128) 128)) - (period-bytes (max fifo-aligned *hda-min-period-bytes*))) - (mezzano.supervisor:debug-print-line - "HDA stream " stream-id " FIFO " fifo-size " period " period-bytes) - period-bytes))) + (or (hda-stable-period-bytes hda) + (let* ((fifo-size (1+ (sd-reg/16 hda stream-id +sdnfifos+))) + (fifo-aligned (* (ceiling fifo-size 128) 128)) + (period-bytes (max fifo-aligned *hda-min-period-bytes*))) + (mezzano.supervisor:debug-print-line + "HDA stream " stream-id " FIFO " fifo-size " period " period-bytes) + period-bytes)))) (defun start-playback (hda buffer buffer-size codec dac pin &optional mixer &key @@ -1129,116 +1129,138 @@ Returns NIL if there is no output path." (format nil " mixer ~D" (nid mixer)) ""))) +(defun hda-restart-stream (hda stream buffer period-bytes period-count buf-len) + "Reset the stream, rewrite BDL entries with the given period size, + prep the stream, and start DMA. Returns nothing." + (with-hda-access (hda) + (stream-reset hda stream) + (dotimes (i period-count) + (write-bdl hda i (+ buffer (* i period-bytes)) period-bytes)) + (prep-stream hda stream 0 period-count buf-len) + (setf (global-reg/32 hda +intctl+) + (logior #x80000000 (ash 1 stream))) + (clear-pending-interrupt hda stream) + (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) + (sys.int::dma-write-barrier) + (stream-go hda stream))) + ;; TODO: This should stream to anything that looks vaugely output-like, instead ;; of a single pin. (defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback) (handler-case (let* ((buffer (hda-dma-buffer-phys hda)) - (output-stream (first-output-stream hda)) - (period-bytes (compute-period-bytes hda output-stream)) - (period-count +playback-period-count+) - (buf-len (* period-bytes period-count)) - (n-samples (truncate period-bytes 2)) - (float-sample-buffer (make-array n-samples :element-type 'single-float)) - (output-pin (default-output-pin hda)) - (buffer-offset 0) - (stop-countdown nil) - (fifo-size 0) - (mute-startup (not *i-want-garbage*)) - (startup-delay (if *i-want-garbage* 0 *hda-startup-periods*)) - (startup-delay-seconds (if *i-want-garbage* 0.0 - (/ (* period-bytes *hda-startup-periods*) - 176400.0))) - (startup-dac-nid nil) - (startup-pin-nid nil) - (startup-cad nil)) - (setf (hda-period-bytes hda) period-bytes) - (unless output-pin + (stream (first-output-stream hda)) + (pb (compute-period-bytes hda stream)) + (pc +playback-period-count+) + (bl (* pb pc)) + (n-samples (truncate pb 2)) + (buf (make-array n-samples :element-type 'single-float)) + (pin (default-output-pin hda)) + (woff 0) + (stop-ct nil) + (fifo 0) + (muted (not *i-want-garbage*)) + (startup (if *i-want-garbage* 0 *hda-startup-periods*)) + (startup-sec (if *i-want-garbage* 0.0 + (/ (* pb *hda-startup-periods*) 176400.0))) + (udac nil) (upin nil) (ucad nil) + (ucd 0) + (ucnt 0)) + (setf (hda-period-bytes hda) pb) + (unless pin (error "No HDA output pin with a playback path found.")) - (labels ((store-sample (sample offset) - (let* ((sample-clamped (max (min sample 1.0f0) -1.0f0)) - (sample-rescaled (if (< sample-clamped 0.0f0) - (* sample-clamped 32768.0f0) - (* sample-clamped 32767.0f0))) - (sample-16bit (truncate sample-rescaled))) - (declare (optimize speed (safety 0)) - (type single-float sample-clamped sample-rescaled) - (type fixnum sample-16bit)) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit) - (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit)))) - (refill-fifo () - (cond ((plusp startup-delay) - ;; Still in muted startup: fill with silence, don't - ;; consume real audio from the sink. - (fill float-sample-buffer 0.0)) - ((funcall buffer-fill-callback float-sample-buffer 0 n-samples) - (setf stop-countdown nil)) - ((not stop-countdown) - (setf stop-countdown *hda-idle-periods*))) - (with-hda-access (hda) - (locally - (declare (optimize speed (safety 0)) - (type (simple-array single-float (*)) float-sample-buffer) - (type fixnum n-samples)) - (dotimes (i n-samples) - (store-sample (aref float-sample-buffer i) i)))) - (sys.int::dma-write-barrier) - (setf buffer-offset (rem (+ buffer-offset period-bytes) buf-len)))) - ;; Zero the used DMA buffer area. - (dotimes (i buf-len) - (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) + (labels ((flush () + (dotimes (i n-samples) + (let* ((s (aref buf i)) + (sc (max (min s 1.0f0) -1.0f0)) + (sr (if (< sc 0.0f0) (* sc 32768.0) (* sc 32767.0))) + (si (truncate sr))) + (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 + buffer (+ woff i i)) (ldb (byte 8 0) si) + (mezzano.supervisor::physical-memref-unsigned-byte-8 + buffer (+ woff i i 1)) (ldb (byte 8 8) si)))) + (sys.int::dma-write-barrier) + (setf woff (rem (+ woff pb) bl))) + (refill () + (cond ((plusp startup) (fill buf 0.0)) + ((funcall buffer-fill-callback buf 0 n-samples) + (setf stop-ct nil)) + ((not stop-ct) + (setf stop-ct *hda-idle-periods*))) + (flush)) + (fill-silence () + (let ((save woff)) + (setf woff 0) + (dotimes (i pc) (fill buf 0.0) (flush)) + (setf woff save))) + (try-unmute () + (when (plusp startup) + (decf startup) + (when (zerop startup) + (mezzano.supervisor:debug-print-line + "HDA startup complete, unmuting") + (with-hda-access (hda) + (command hda ucad udac #x3b07f) + (command hda ucad upin #x3b07f) + (command hda ucad upin #x70740)))))) + ;; Init. + (dotimes (i bl) (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0)) (with-hda-access (hda) - (clear-pending-interrupt hda output-stream) + (clear-pending-interrupt hda stream) (mezzano.supervisor:simple-irq-unmask (hda-irq hda)) - (setf fifo-size (1+ (sd-reg/16 hda output-stream +sdnfifos+)))) - ;; Pre-fill all periods before starting DMA. - (unless mute-startup - (dotimes (i period-count) - (refill-fifo))) - ;; Begin playback. - (multiple-value-bind (converter mixer) - (output-path output-pin) - (unless converter - (error "HDA output pin ~D has no converter." (nid output-pin))) - (log-selected-output-path output-pin converter mixer) - (set-widget-power-state-d0 output-pin) - (set-widget-power-state-d0 converter) - (when mixer - (set-widget-power-state-d0 mixer)) - (maybe-enable-eapd output-pin) - (when mute-startup - (setf startup-dac-nid (nid converter) - startup-pin-nid (nid output-pin) - startup-cad (cad output-pin))) - (start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer)) - :period-bytes period-bytes - :period-count period-count - :startup-delay-seconds startup-delay-seconds - :mute-for-startup mute-startup)) + (setf fifo (1+ (sd-reg/16 hda stream +sdnfifos+)))) + (unless muted (dotimes (i pc) (refill))) + ;; Output path + start. + (multiple-value-bind (c m) (output-path pin) + (unless c (error "HDA output pin ~D has no converter." (nid pin))) + (log-selected-output-path pin c m) + (set-widget-power-state-d0 pin) + (set-widget-power-state-d0 c) + (when m (set-widget-power-state-d0 m)) + (maybe-enable-eapd pin) + (when muted + (setf udac (nid c) upin (nid pin) ucad (cad pin))) + (start-playback hda buffer bl (cad pin) (nid c) (nid pin) + (and m (nid m)) + :period-bytes pb :period-count pc + :startup-delay-seconds startup-sec + :mute-for-startup muted)) (mezzano.supervisor:debug-print-line - "HDA entering loop, lpib=" (sd-reg/32 hda output-stream +sdnlpib+)) + "HDA entering loop, lpib=" (sd-reg/32 hda stream +sdnlpib+)) + ;; Loop. (unwind-protect (loop (ecase (wait-for-buffer-interrupt hda) (:timeout) - (:interrupt - (let* ((lpib (sd-reg/32 hda output-stream +sdnlpib+)) - (lpib-fifo (mod (+ lpib fifo-size) buf-len)) - (current-offset (truncate lpib-fifo period-bytes))) - (when (not (eql current-offset (truncate buffer-offset period-bytes))) - (when stop-countdown - (when (zerop stop-countdown) (return)) - (decf stop-countdown)) - (refill-fifo) - (when (plusp startup-delay) - (decf startup-delay) - (when (zerop startup-delay) - (mezzano.supervisor:debug-print-line - "HDA startup complete, unmuting") - (with-hda-access (hda) - (command hda startup-cad startup-dac-nid #x3b07f) - (command hda startup-cad startup-pin-nid #x3b07f) - (command hda startup-cad startup-pin-nid #x70740))))))))) + (:interrupt + (let* ((lp (sd-reg/32 hda stream +sdnlpib+)) + (lpf (mod (+ lp fifo) bl)) + (co (truncate lpf pb))) + (unless (eql co (truncate woff pb)) + (when stop-ct + (when (zerop stop-ct) (return)) + (decf stop-ct)) + (refill) + ;; If the hardware crossed into the next period + ;; while we were refilling, we're falling behind. + (let* ((lp2 (sd-reg/32 hda stream +sdnlpib+)) + (lpf2 (mod (+ lp2 fifo) bl)) + (co2 (truncate lpf2 pb))) + (when (not (eql co2 co)) + (incf ucnt))) + (try-unmute))))) + (when (plusp ucd) (decf ucd)) + (when (and (zerop ucd) (> ucnt 3)) + (let ((npb (* (ceiling (+ pb 128) 128) 128))) + (mezzano.supervisor:debug-print-line + "HDA underrun, period " pb " → " npb) + (setf (hda-stable-period-bytes hda) npb + pb npb (hda-period-bytes hda) npb + bl (* npb pc) n-samples (truncate npb 2) + buf (make-array n-samples :element-type 'single-float) + woff 0 ucd (* 8 pc) ucnt 0) + (hda-restart-stream hda stream buffer pb pc bl) + (fill-silence)))) (with-hda-access (hda) (stream-reset hda (first-output-stream hda)) (mezzano.supervisor:simple-irq-mask (hda-irq hda)))))) @@ -1252,5 +1274,5 @@ Returns NIL if there is no output path." (pci:define-pci-driver intel-hda intel-hda-probe ((#x8086 #x2668) ; ICH6 HDA (#x8086 #x27D8) ; ICH7 HDA - (#x8086 #x293E)) ; ICH9 HDA + (#x8086 #x293E)); ICH9 HDA ()) From d1930247d76f6de6857698353ee3f9a56c3fa7ba Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Fri, 15 May 2026 04:09:47 +0500 Subject: [PATCH 14/15] music-player: fix leak --- gui/music-player.lisp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/gui/music-player.lisp b/gui/music-player.lisp index 8ac013feb..42833aad8 100644 --- a/gui/music-player.lisp +++ b/gui/music-player.lisp @@ -10,6 +10,7 @@ ((%fifo :initarg :fifo :reader fifo) (%window :initarg :window :reader window) (%audio-thread :initarg :audio-thread :accessor audio-thread) + (%reader-thread :initarg :reader-thread :accessor reader-thread) (%font :initarg :font :reader font) (%frame :initarg :frame :reader frame) (%stream :initarg :stream :reader player-stream) @@ -105,18 +106,19 @@ ;; Reader thread: reads remaining chunks into FIFO while ;; the main thread pushes them to the sink. Reading and ;; playback overlap, so a slow read never drains the sink. - (mezzano.supervisor:make-thread - (lambda () - (unwind-protect - (loop - (when (>= current-position data-size) (return)) - (let* ((n-bytes (min chunk-size (- data-size current-position))) - (buf (make-array n-bytes :element-type '(unsigned-byte 8)))) - (read-sequence buf stream) - (incf current-position n-bytes) - (mezzano.supervisor:fifo-push buf chunk-fifo))) - (mezzano.supervisor:fifo-push nil chunk-fifo))) - :name (format nil "Audio reader for ~S" player)) + (setf (reader-thread player) + (mezzano.supervisor:make-thread + (lambda () + (unwind-protect + (loop + (when (>= current-position data-size) (return)) + (let* ((n-bytes (min chunk-size (- data-size current-position))) + (buf (make-array n-bytes :element-type '(unsigned-byte 8)))) + (read-sequence buf stream) + (incf current-position n-bytes) + (mezzano.supervisor:fifo-push buf chunk-fifo))) + (mezzano.supervisor:fifo-push nil chunk-fifo))) + :name (format nil "Audio reader for ~S" player))) (unwind-protect (loop (let ((buf (mezzano.supervisor:fifo-pop chunk-fifo))) @@ -186,7 +188,8 @@ (mezzano.gui.widgets:close-button-clicked () (return-from main))))) (when (audio-thread player) - (mezzano.supervisor:terminate-thread (audio-thread player))) + (mezzano.supervisor:terminate-thread (audio-thread player)) + (mezzano.supervisor:terminate-thread (reader-thread player))) (mezzano.supervisor:with-mutex ((worker-comm-lock player)) (setf (playback-state player) :exit) (mezzano.supervisor:condition-notify (worker-comm-cvar player)))))))))) From 32bc28b9dd0563aaca368e75ecdae6f665f4debf Mon Sep 17 00:00:00 2001 From: Iska Mag Date: Sun, 17 May 2026 17:34:30 +0500 Subject: [PATCH 15/15] sound: add unwind-protect --- drivers/sound.lisp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/drivers/sound.lisp b/drivers/sound.lisp index 0d8dd54ad..9ea8878f0 100644 --- a/drivers/sound.lisp +++ b/drivers/sound.lisp @@ -136,22 +136,21 @@ ;; buffer is already zeroed, so the HDA plays silence for this ;; period. Next period will retry. (if (mezzano.supervisor:acquire-mutex *sink-lock* nil) - (let ((result - (cond ((endp *sinks*) - nil) - (t - (dolist (sink *sinks*) - (mix-out-of-sink sink buffer start end)) - (setf *sinks* (delete-if - (lambda (sink) - (buffer-empty sink)) - *sinks*)) - (when (endp *sinks*) - (setf (sup:event-state *sinks-present-event*) nil)) - (mezzano.supervisor:condition-notify *sink-cvar* t) - t)))) - (mezzano.supervisor:release-mutex *sink-lock*) - result) + (unwind-protect + (cond ((endp *sinks*) + nil) + (t + (dolist (sink *sinks*) + (mix-out-of-sink sink buffer start end)) + (setf *sinks* (delete-if + (lambda (sink) + (buffer-empty sink)) + *sinks*)) + (when (endp *sinks*) + (setf (sup:event-state *sinks-present-event*) nil)) + (mezzano.supervisor:condition-notify *sink-cvar* t) + t)) + (mezzano.supervisor:release-mutex *sink-lock*)) t))) (defun sound-worker (device)