mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
oss: RetroFW writes whole 16-byte DMA units
RetroFW's jz4760 OSS driver moves a node to the codec in 16-byte DMA units: it drops a write's remainder, and a write under one unit starts a transfer of none. The threaded pipeline writes any number of frames, so most writes lost one to three frames - static at its rate of writes - and a short one could leave the device playing nothing it was given. On RetroFW a write now goes out in whole units, its remainder held to lead the next one. samples/audio/oss_report models that DMA; built with RETROFW=1, which CI now does, it checks every node is whole units and plays in order. Fixes #20006
This commit is contained in:
1 parent
391dbc91b1
commit
7e92aa944e
4 files changed
+136
-2
No files matched your search
@@ -633,6 +633,10 @@ jobs:
|
||||
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./oss_report_test
|
||||
echo "[pass] oss_report_test (ASan)"
|
||||
# RetroFW's jz4760 driver plays whole 16-byte DMA units only.
|
||||
make clean && make -j$(nproc) all RETROFW=1 SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./oss_report_test
|
||||
echo "[pass] oss_report_test (RetroFW, ASan)"
|
||||
|
||||
- name: Build and run live_drivers_test (a live PulseAudio server, a null ALSA PCM)
|
||||
shell: bash
|
||||
|
||||
@@ -76,6 +76,15 @@ typedef struct oss_audio
|
||||
* wait for room is the blocking write's, in the driver, on its own
|
||||
* fragment-done interrupt. */
|
||||
bool select_blind;
|
||||
#ifdef RETROFW
|
||||
/* jz4760's DMA moves 16-byte units: it drops a write's remainder,
|
||||
* and a write under one unit starts a transfer of none. Writes go
|
||||
* out in whole units; the remainder leads the next one. */
|
||||
uint8_t *units;
|
||||
size_t units_bytes;
|
||||
uint8_t carry[16];
|
||||
size_t carry_bytes;
|
||||
#endif
|
||||
} oss_audio_t;
|
||||
|
||||
#define OSS_FRAME_BYTES (ossaudio->channels * sizeof(int16_t))
|
||||
@@ -84,6 +93,59 @@ typedef struct oss_audio
|
||||
* space, so one call costs at most this many bounded waits. */
|
||||
#define OSS_WAIT_WRITABLE_LAPS 8
|
||||
|
||||
#ifdef RETROFW
|
||||
#define OSS_DMA_UNIT 16
|
||||
|
||||
/* @len bytes behind the carry, whole units to the device. Returns the
|
||||
* bytes of @s taken, the remainder held among them. */
|
||||
static ssize_t oss_write_units(oss_audio_t *ossaudio,
|
||||
const uint8_t *s, size_t len)
|
||||
{
|
||||
ssize_t w;
|
||||
size_t k = ossaudio->carry_bytes;
|
||||
size_t total = k + len;
|
||||
size_t whole = total & ~(size_t)(OSS_DMA_UNIT - 1);
|
||||
const uint8_t *out = s;
|
||||
|
||||
if (!whole)
|
||||
{
|
||||
memcpy(ossaudio->carry + k, s, len);
|
||||
ossaudio->carry_bytes = total;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
if (k)
|
||||
{
|
||||
if (ossaudio->units_bytes < whole)
|
||||
{
|
||||
uint8_t *n = (uint8_t*)realloc(ossaudio->units, whole);
|
||||
if (!n)
|
||||
return -1;
|
||||
ossaudio->units = n;
|
||||
ossaudio->units_bytes = whole;
|
||||
}
|
||||
memcpy(ossaudio->units, ossaudio->carry, k);
|
||||
memcpy(ossaudio->units + k, s, whole - k);
|
||||
out = ossaudio->units;
|
||||
}
|
||||
if ((w = write(ossaudio->fd, out, whole)) < 0)
|
||||
{
|
||||
if (errno == EAGAIN && (fcntl(ossaudio->fd, F_GETFL) & O_NONBLOCK))
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
if (!w)
|
||||
return 0;
|
||||
ossaudio->frames_written += (uint64_t)w / OSS_FRAME_BYTES;
|
||||
ossaudio->carry_bytes = 0;
|
||||
/* Short, the rest stays with the caller; whole units always. */
|
||||
if ((size_t)w < whole)
|
||||
return w - (ssize_t)k;
|
||||
memcpy(ossaudio->carry, s + (whole - k), total - whole);
|
||||
ossaudio->carry_bytes = total - whole;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *oss_init(const char *device,
|
||||
unsigned rate, unsigned latency,
|
||||
|
||||
@@ -212,6 +274,9 @@ static ssize_t oss_write(void *data, const void *s, size_t len)
|
||||
s = ossaudio->stage;
|
||||
len = frames * OSS_FRAME_BYTES;
|
||||
}
|
||||
#ifdef RETROFW
|
||||
return oss_write_units(ossaudio, (const uint8_t*)s, len);
|
||||
#endif
|
||||
if ((_len = write(ossaudio->fd, s, len)) < 0)
|
||||
{
|
||||
if (errno == EAGAIN && (fcntl(ossaudio->fd, F_GETFL) & O_NONBLOCK))
|
||||
@@ -327,6 +392,9 @@ static bool oss_stop(void *data)
|
||||
#endif
|
||||
|
||||
ossaudio->is_paused = true;
|
||||
#ifdef RETROFW
|
||||
ossaudio->carry_bytes = 0;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -377,6 +445,9 @@ static void oss_free(void *data)
|
||||
"its blocking write did.\n");
|
||||
close(ossaudio->fd);
|
||||
free(ossaudio->stage);
|
||||
#ifdef RETROFW
|
||||
free(ossaudio->units);
|
||||
#endif
|
||||
free(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ LDFLAGS += -lm
|
||||
# ioctl, select, write and close on the modelled device's descriptor
|
||||
# are the test's; every other descriptor goes to the real call.
|
||||
LDFLAGS += -Wl,--wrap=ioctl,--wrap=select,--wrap=write,--wrap=close
|
||||
# RetroFW's driver, whose DMA moves whole 16-byte units
|
||||
ifneq ($(RETROFW),)
|
||||
CFLAGS += -DRETROFW=1
|
||||
endif
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer -g1 $(CFLAGS)
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
* free() on a device whose SNDCTL_DSP_RESET fails returned before
|
||||
* closing it, leaking the descriptor, the stage and the driver.
|
||||
*
|
||||
* Built with RETROFW=1: that driver's DMA moves 16-byte units, drops a
|
||||
* node's remainder and starts no transfer for a node under one unit.
|
||||
* The threaded pipeline writes any number of frames, and every
|
||||
* remainder dropped was a click - static, at its rate of writes.
|
||||
*
|
||||
* The driver is this translation unit; a closed descriptor makes the
|
||||
* real ioctl fail, and ioctl, select, write and close are wrapped
|
||||
* (--wrap) so one descriptor is the modelled device. */
|
||||
@@ -64,6 +69,10 @@ static unsigned dev_write_sleeps;
|
||||
* that waits for a fragment and times out on a full device (0) */
|
||||
static int dev_select_blind;
|
||||
static unsigned dev_closes;
|
||||
/* What the DMA played, and the nodes it could not play whole */
|
||||
static uint8_t dev_played[1 << 16];
|
||||
static size_t dev_played_bytes;
|
||||
static unsigned dev_cut_nodes;
|
||||
|
||||
int __real_ioctl(int fd, unsigned long req, ...);
|
||||
int __real_select(int n, fd_set *r, fd_set *w, fd_set *e,
|
||||
@@ -131,14 +140,29 @@ ssize_t __wrap_write(int fd, const void *buf, size_t len)
|
||||
dev_free_nodes++;
|
||||
}
|
||||
dev_free_nodes--;
|
||||
done += n;
|
||||
if (n % 16)
|
||||
dev_cut_nodes++;
|
||||
n -= n % 16;
|
||||
if (dev_played_bytes + n <= sizeof(dev_played))
|
||||
memcpy(dev_played + dev_played_bytes,
|
||||
(const uint8_t*)buf + done, n);
|
||||
dev_played_bytes += n;
|
||||
done += len - done > DEV_FRAGSIZE ? DEV_FRAGSIZE : len - done;
|
||||
}
|
||||
return (ssize_t)done;
|
||||
}
|
||||
|
||||
static void dev_close(oss_audio_t *oss)
|
||||
{
|
||||
#ifdef RETROFW
|
||||
free(oss->units);
|
||||
#endif
|
||||
memset(oss, 0, sizeof(*oss));
|
||||
}
|
||||
|
||||
static void dev_open(oss_audio_t *oss, int blind)
|
||||
{
|
||||
memset(oss, 0, sizeof(*oss));
|
||||
dev_close(oss);
|
||||
oss->fd = DEV_FD;
|
||||
oss->rate = 32000;
|
||||
oss->channels = 2;
|
||||
@@ -146,6 +170,8 @@ static void dev_open(oss_audio_t *oss, int blind)
|
||||
dev_selects = 0;
|
||||
dev_write_sleeps = 0;
|
||||
dev_select_blind = blind;
|
||||
dev_played_bytes = 0;
|
||||
dev_cut_nodes = 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
@@ -214,6 +240,35 @@ int main(void)
|
||||
CHECK(dev_selects == 1, "after one bounded wait", dev_selects, 1);
|
||||
CHECK(!oss.select_blind, "and is not latched as poll-less", 1, 0);
|
||||
|
||||
#ifdef RETROFW
|
||||
/* Writes of any number of frames - one, three, a publish - play
|
||||
* whole and in order, the last few frames held for the next. */
|
||||
{
|
||||
static uint8_t in[16384];
|
||||
static const size_t frames[] = { 533, 1, 3, 2, 534, 7, 1066, 5 };
|
||||
size_t i, off = 0;
|
||||
for (i = 0; i < sizeof(in); i++)
|
||||
in[i] = (uint8_t)(i * 7 + 3);
|
||||
dev_open(&oss, 1);
|
||||
dev_free_nodes = DEV_FRAGS;
|
||||
for (i = 0; i < sizeof(frames) / sizeof(frames[0]); i++)
|
||||
{
|
||||
size_t bytes = frames[i] * 4;
|
||||
CHECK(oss_write(&oss, in + off, bytes) == (ssize_t)bytes,
|
||||
"a write is taken whole", i, bytes);
|
||||
off += bytes;
|
||||
}
|
||||
CHECK(dev_cut_nodes == 0, "every node is whole DMA units",
|
||||
dev_cut_nodes, 0);
|
||||
CHECK(dev_played_bytes <= off && off - dev_played_bytes < 16,
|
||||
"all but a remainder under a unit is played",
|
||||
dev_played_bytes, off);
|
||||
CHECK(!memcmp(dev_played, in, dev_played_bytes),
|
||||
"what plays is what was written, in order", 0, 0);
|
||||
}
|
||||
#endif
|
||||
dev_close(&oss);
|
||||
|
||||
/* SNDCTL_DSP_RESET fails on the modelled device: free() still closes
|
||||
* it, and LeakSanitizer sees the stage and the driver freed. */
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user