SWCODEC: Get rid of extra swap buffer and get back 512K of RAM or 100K if the players RAM is <= 1MB. Make any needed changes to things to stabilize and facilitate this including removing flattening out initialization. Comment some things heavily. Fix a few logfs I didn't want to see the compiler complaining about.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12843 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Michael Sevakis 2007-03-19 22:04:17 +00:00
parent fbf52ae8fe
commit e1dd10ddfb
10 changed files with 578 additions and 241 deletions

View file

@ -58,7 +58,10 @@ struct pcmbufdesc
void (*callback)(void);
};
#define PCMBUF_DESCS(bufsize) ((bufsize) / PCMBUF_MINAVG_CHUNK)
#define PCMBUF_DESCS(bufsize) \
((bufsize) / PCMBUF_MINAVG_CHUNK)
#define PCMBUF_DESCS_SIZE(bufsize) \
(PCMBUF_DESCS(bufsize)*sizeof(struct pcmbufdesc))
/* Size of the PCM buffer. */
static size_t pcmbuf_size IDATA_ATTR = 0;
@ -76,6 +79,7 @@ static void (*position_callback)(size_t size) IDATA_ATTR;
/* Crossfade related state */
static bool crossfade_enabled;
static bool crossfade_enabled_pending;
static bool crossfade_mixmode;
static bool crossfade_active IDATA_ATTR;
static bool crossfade_init IDATA_ATTR;
@ -187,9 +191,13 @@ void pcmbuf_set_position_callback(void (*callback)(size_t size))
position_callback = callback;
}
static void pcmbuf_set_watermark_bytes(size_t numbytes)
static void pcmbuf_set_watermark_bytes(void)
{
pcmbuf_watermark = numbytes;
pcmbuf_watermark = (crossfade_enabled && pcmbuf_size) ?
/* If crossfading, try to keep the buffer full other than 1 second */
(pcmbuf_size - (NATIVE_FREQUENCY * 4 * 1)) :
/* Otherwise, just keep it above 2 second */
PCMBUF_WATERMARK;
}
/* This is really just part of pcmbuf_flush_fillpos, but is easier to keep
@ -413,30 +421,60 @@ static void pcmbuf_init_pcmbuffers(void) {
}
}
bool pcmbuf_is_same_size(size_t bufsize)
static size_t pcmbuf_get_next_required_pcmbuf_size(void)
{
/* keep calculations synced with pcmbuf_init */
bufsize += PCMBUF_MIX_CHUNK * 2 +
PCMBUF_DESCS(bufsize)*sizeof(struct pcmbufdesc);
return bufsize == (size_t)(pcmbuf_bufend - audiobuffer);
#if MEM > 1
size_t seconds = 1;
if (crossfade_enabled_pending)
seconds += global_settings.crossfade_fade_out_delay
+ global_settings.crossfade_fade_out_duration;
/* Buffer has to be at least 2s long. */
seconds += 2;
logf("pcmbuf len: %ld", seconds);
return seconds * (NATIVE_FREQUENCY*4);
#else
return NATIVE_FREQUENCY*2;
#endif
}
static char *pcmbuf_calc_audiobuffer_ptr(size_t bufsize)
{
return pcmbuf_bufend - (bufsize + PCMBUF_MIX_CHUNK * 2 +
PCMBUF_DESCS_SIZE(bufsize));
}
bool pcmbuf_is_same_size(void)
{
if (audiobuffer == NULL)
return true; /* Not set up yet even once so always */
size_t bufsize = pcmbuf_get_next_required_pcmbuf_size();
return pcmbuf_calc_audiobuffer_ptr(bufsize) == audiobuffer;
}
/* Initialize the pcmbuffer the structure looks like this:
* ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */
size_t pcmbuf_init(size_t bufsize, char *bufend)
size_t pcmbuf_init(unsigned char *bufend)
{
pcmbuf_size = bufsize;
pcmbuf_bufend = bufend;
pcmbuf_descsize = pcmbuf_descs()*sizeof(struct pcmbufdesc);
audiobuffer = pcmbuf_bufend - (pcmbuf_size + PCMBUF_MIX_CHUNK * 2
+ pcmbuf_descsize);
pcmbuf_size = pcmbuf_get_next_required_pcmbuf_size();
audiobuffer = pcmbuf_calc_audiobuffer_ptr(pcmbuf_size);
fadebuf = &audiobuffer[pcmbuf_size];
voicebuf = &fadebuf[PCMBUF_MIX_CHUNK];
pcmbuf_write = (struct pcmbufdesc *)&voicebuf[PCMBUF_MIX_CHUNK];
pcmbuf_descsize = PCMBUF_DESCS_SIZE(pcmbuf_size);
pcmbuf_init_pcmbuffers();
position_callback = NULL;
pcmbuf_event_handler = NULL;
pcmbuf_crossfade_enable_finished();
pcmbuf_play_stop();
return pcmbuf_bufend - audiobuffer;
}
@ -445,6 +483,14 @@ size_t pcmbuf_get_bufsize(void)
return pcmbuf_size;
}
#ifdef ROCKBOX_HAS_LOGF
unsigned char * pcmbuf_get_meminfo(size_t *length)
{
*length = pcmbuf_bufend - audiobuffer;
return audiobuffer;
}
#endif
void pcmbuf_pause(bool pause) {
#ifdef PCMBUF_MUTING
if (pause)
@ -1036,15 +1082,18 @@ void pcmbuf_mix_voice(int count)
void pcmbuf_crossfade_enable(bool on_off)
{
crossfade_enabled = on_off;
#if MEM > 1
/* Next setting to be used, not applied now */
crossfade_enabled_pending = on_off;
#endif
(void)on_off;
}
if (crossfade_enabled) {
/* If crossfading, try to keep the buffer full other than 1 second */
pcmbuf_set_watermark_bytes(pcmbuf_size - (NATIVE_FREQUENCY * 4 * 1));
} else {
/* Otherwise, just keep it above 2 second */
pcmbuf_set_watermark_bytes(PCMBUF_WATERMARK);
}
void pcmbuf_crossfade_enable_finished(void)
{
/* Copy the pending setting over now */
crossfade_enabled = crossfade_enabled_pending;
pcmbuf_set_watermark_bytes();
}
bool pcmbuf_is_crossfade_enabled(void)
@ -1054,4 +1103,3 @@ bool pcmbuf_is_crossfade_enabled(void)
return crossfade_enabled;
}

View file

@ -37,10 +37,14 @@
for mixing (crossfade or voice) */
/* Returns true if the buffer needs to change size */
bool pcmbuf_is_same_size(size_t bufsize);
size_t pcmbuf_init(size_t bufsize, char *bufend);
bool pcmbuf_is_same_size(void);
size_t pcmbuf_init(unsigned char *bufend);
/* Size in bytes used by the pcmbuffer */
size_t pcmbuf_get_bufsize(void);
#ifdef ROCKBOX_HAS_LOGF
/* just used for logging for now */
unsigned char * pcmbuf_get_meminfo(size_t *length);
#endif
size_t get_pcmbuf_descsize(void);
void pcmbuf_pause(bool pause);
@ -68,7 +72,7 @@ void* pcmbuf_request_buffer(int *count);
void* pcmbuf_request_voice_buffer(int *count, bool mix);
bool pcmbuf_is_crossfade_enabled(void);
void pcmbuf_crossfade_enable(bool on_off);
void pcmbuf_crossfade_enable_finished(void);
int pcmbuf_usage(void);
int pcmbuf_mix_free(void);
void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude);

View file

@ -34,6 +34,8 @@
#include "system.h"
#include "thread.h"
#include "file.h"
#include "panic.h"
#include "memory.h"
#include "lcd.h"
#include "font.h"
#include "button.h"
@ -167,14 +169,14 @@ enum {
/* As defined in plugin.lds */
#if defined(CPU_PP)
#define CODEC_IRAM_ORIGIN 0x4000c000
#define CODEC_IRAM_SIZE 0xc000
#define CODEC_IRAM_ORIGIN ((unsigned char *)0x4000c000)
#define CODEC_IRAM_SIZE ((size_t)0xc000)
#elif defined(IAUDIO_X5) || defined(IAUDIO_M5)
#define CODEC_IRAM_ORIGIN 0x10010000
#define CODEC_IRAM_SIZE 0x10000
#define CODEC_IRAM_ORIGIN ((unsigned char *)0x10010000)
#define CODEC_IRAM_SIZE ((size_t)0x10000)
#else
#define CODEC_IRAM_ORIGIN 0x1000c000
#define CODEC_IRAM_SIZE 0xc000
#define CODEC_IRAM_ORIGIN ((unsigned char *)0x1000c000)
#define CODEC_IRAM_SIZE ((size_t)0xc000)
#endif
#ifndef IBSS_ATTR_VOICE_STACK
@ -205,7 +207,7 @@ static volatile size_t buf_widx IDATA_ATTR = 0; /* Buffer write position (A/C-)
/* Possible arrangements of the buffer */
#define BUFFER_STATE_TRASHED -1 /* trashed; must be reset */
#define BUFFER_STATE_NORMAL 0 /* voice+audio OR audio-only */
#define BUFFER_STATE_INITIALIZED 0 /* voice+audio OR audio-only */
#define BUFFER_STATE_VOICED_ONLY 1 /* voice-only */
static int buffer_state = BUFFER_STATE_TRASHED; /* Buffer state */
@ -282,7 +284,7 @@ static const char audio_thread_name[] = "audio";
static void audio_thread(void);
static void audio_initiate_track_change(long direction);
static bool audio_have_tracks(void);
static void audio_reset_buffer(size_t pcmbufsize);
static void audio_reset_buffer(void);
/* Codec thread */
extern struct codec_api ci;
@ -315,10 +317,16 @@ static unsigned char sim_iram[CODEC_IRAM_SIZE];
#define CODEC_IRAM_ORIGIN sim_iram
#endif
/* Pointer to IRAM buffers for normal/voice codecs */
static unsigned char *iram_buf[2] = { NULL, NULL };
/* Pointer to DRAM buffers for normal/voice codecs */
static unsigned char *dram_buf[2] = { NULL, NULL };
/* iram_buf and dram_buf are either both NULL or both non-NULL */
/* Pointer to IRAM buffer for codec swapping */
static unsigned char *iram_buf = NULL;
/* Pointer to DRAM buffer for codec swapping */
static unsigned char *dram_buf = NULL;
/* Parity of swap_codec calls - needed because one codec swapping itself in
automatically swaps in the other and the swap when unlocking should not
happen if the parity is even.
*/
static bool swap_codec_parity = false; /* true=odd, false=even */
/* Mutex to control which codec (normal/voice) is running */
static struct mutex mutex_codecthread NOCACHEBSS_ATTR;
@ -342,6 +350,7 @@ struct voice_info {
char *buf;
};
static void voice_thread(void);
static void voice_stop(void);
#endif /* PLAYBACK_VOICE */
@ -404,7 +413,7 @@ void mpeg_id3_options(bool _v1first)
static void wait_for_voice_swap_in(void)
{
#ifdef PLAYBACK_VOICE
if (NULL == iram_buf[CODEC_IDX_VOICE])
if (NULL == iram_buf)
return;
while (current_codec != CODEC_IDX_VOICE)
@ -426,7 +435,8 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size)
if (buffer_size == NULL)
{
/* Special case for talk_init to use */
/* Special case for talk_init to use since it already knows it's
trashed */
buffer_state = BUFFER_STATE_TRASHED;
return NULL;
}
@ -434,8 +444,15 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size)
if (talk_buf || buffer_state == BUFFER_STATE_TRASHED
|| !talk_voice_required())
{
logf("get buffer: talk_buf");
/* ok to use everything from audiobuf to audiobufend */
logf("get buffer: talk, audio");
/* Ok to use everything from audiobuf to audiobufend - voice is loaded,
the talk buffer is not needed because voice isn't being used, or
could be BUFFER_STATE_TRASHED already. If state is
BUFFER_STATE_VOICED_ONLY, no problem as long as memory isn't written
without the caller knowing what's going on. Changing certain settings
may move it to a worse condition but the memory in use by something
else will remain undisturbed.
*/
if (buffer_state != BUFFER_STATE_TRASHED)
{
talk_buffer_steal();
@ -447,10 +464,14 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size)
}
else
{
/* skip talk buffer and move pcm buffer to end */
logf("get buffer: voice");
/* Safe to just return this if already BUFFER_STATE_VOICED_ONLY or
still BUFFER_STATE_INITIALIZED */
/* Skip talk buffer and move pcm buffer to end to maximize available
contiguous memory - no audio running means voice will not need the
swap space */
logf("get buffer: audio");
buf = audiobuf + talk_get_bufsize();
end = audiobufend - pcmbuf_init(pcmbuf_get_bufsize(), audiobufend);
end = audiobufend - pcmbuf_init(audiobufend);
buffer_state = BUFFER_STATE_VOICED_ONLY;
}
@ -466,18 +487,22 @@ void audio_iram_steal(void)
audio_stop();
#ifdef PLAYBACK_VOICE
if (NULL != iram_buf[CODEC_IDX_VOICE])
if (NULL != iram_buf)
{
/* Can't already be stolen */
if (voice_iram_stolen)
return;
/* Must wait for voice to be current again if it is swapped which
would cause the caller's buffer to get clobbered when voice locks
and runs - we'll wait for it to lock and yield again then make sure
the ride has come to a complete stop */
wait_for_voice_swap_in();
voice_stop();
/* Save voice IRAM - safe to do here since state is known */
memcpy(iram_buf[CODEC_IDX_VOICE], (void *)CODEC_IRAM_ORIGIN,
CODEC_IRAM_SIZE);
/* Save voice IRAM but just memcpy - safe to do here since voice
is current and no audio codec is loaded */
memcpy(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE);
voice_iram_stolen = true;
}
else
@ -492,21 +517,23 @@ void audio_iram_steal(void)
#ifdef HAVE_RECORDING
unsigned char *audio_get_recording_buffer(size_t *buffer_size)
{
/* don't allow overwrite of voice swap area or we'll trash the
/* Don't allow overwrite of voice swap area or we'll trash the
swapped-out voice codec but can use whole thing if none */
unsigned char *end;
/* Stop audio and voice. Wait for voice to swap in and be clear
of pending events to ensure trouble-free operation of encoders */
audio_stop();
wait_for_voice_swap_in();
voice_stop();
talk_buffer_steal();
#ifdef PLAYBACK_VOICE
#ifdef IRAM_STEAL
end = dram_buf[CODEC_IDX_VOICE];
#else
end = iram_buf[CODEC_IDX_VOICE];
#endif /* IRAM_STEAL */
/* If no dram_buf, swap space not used and recording gets more
memory. Codec swap areas will remain unaffected by the next init
since they're allocated at the end of the buffer and their sizes
don't change between calls */
end = dram_buf;
if (NULL == end)
#endif /* PLAYBACK_VOICE */
end = audiobufend;
@ -781,101 +808,51 @@ void audio_set_buffer_margin(int setting)
{
static const int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600};
buffer_margin = lookup[setting];
logf("buffer margin: %ds", buffer_margin);
logf("buffer margin: %ld", buffer_margin);
set_filebuf_watermark(buffer_margin);
}
/* Set crossfade & PCM buffer length. */
/* Take nescessary steps to enable or disable the crossfade setting */
void audio_set_crossfade(int enable)
{
size_t offset;
bool was_playing;
size_t size;
bool was_playing = (playing && audio_is_initialized);
size_t offset = 0;
#if MEM > 1
int seconds = 1;
#endif
if (!filebuf)
return; /* Audio buffers not yet set up */
/* Tell it the next setting to use */
pcmbuf_crossfade_enable(enable);
#if MEM > 1
if (enable)
seconds = global_settings.crossfade_fade_out_delay
+ global_settings.crossfade_fade_out_duration;
/* Return if size hasn't changed or this is too early to determine
which in the second case there's no way we could be playing
anything at all */
if (pcmbuf_is_same_size())
{
/* This function is a copout and just syncs some variables -
to be removed at a later date */
pcmbuf_crossfade_enable_finished();
return;
}
/* Buffer has to be at least 2s long. */
seconds += 2;
logf("buf len: %d", seconds);
size = seconds * (NATIVE_FREQUENCY*4);
#else
enable = 0;
size = NATIVE_FREQUENCY*2;
#endif
if (buffer_state == BUFFER_STATE_NORMAL && pcmbuf_is_same_size(size))
return ;
offset = 0;
was_playing = playing;
/* Playback has to be stopped before changing the buffer size */
if (was_playing)
{
/* Store the track resume position */
offset = CUR_TI->id3.offset;
/* Playback has to be stopped before changing the buffer size. */
gui_syncsplash(0, (char *)str(LANG_RESTARTING_PLAYBACK));
audio_stop();
gui_syncsplash(0, str(LANG_RESTARTING_PLAYBACK));
}
voice_stop();
/* Blast it - audio buffer will have to be setup again next time
something plays */
audio_get_buffer(true, &size);
/* Re-initialize audio system. */
audio_reset_buffer(size);
pcmbuf_crossfade_enable(enable);
logf("abuf:%dB", pcmbuf_get_bufsize());
logf("fbuf:%dB", filebuflen);
voice_init();
/* Restart playback. */
if (was_playing)
/* Restart playback if audio was running previously */
if (was_playing)
audio_play(offset);
}
void voice_init(void)
{
#ifdef PLAYBACK_VOICE
if (voice_thread_p || !filebuf || voice_codec_loaded ||
!talk_voice_required())
return;
logf("Starting voice codec");
queue_init(&voice_queue, true);
voice_thread_p = create_thread(voice_thread, voice_stack,
sizeof(voice_stack), voice_thread_name
IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU, false));
while (!voice_codec_loaded)
yield();
#endif
} /* voice_init */
void voice_stop(void)
{
#ifdef PLAYBACK_VOICE
/* Messages should not be posted to voice codec queue unless it is the
current codec or deadlocks happen. */
if (current_codec != CODEC_IDX_VOICE)
return;
LOGFQUEUE("mp3 > voice Q_VOICE_STOP");
queue_post(&voice_queue, Q_VOICE_STOP, 0);
while (voice_is_playing || !queue_empty(&voice_queue))
yield();
if (!playing)
pcmbuf_play_stop();
#endif
} /* voice_stop */
/* --- Routines called from multiple threads --- */
static void set_current_codec(int codec_idx)
{
@ -886,35 +863,61 @@ static void set_current_codec(int codec_idx)
#ifdef PLAYBACK_VOICE
static void swap_codec(void)
{
int my_codec = current_codec;
int my_codec;
logf("swapping out codec:%d", my_codec);
/* Save our current IRAM and DRAM */
#ifdef IRAM_STEAL
if (voice_iram_stolen)
/* Swap nothing if no swap buffers exist */
if (dram_buf == NULL)
{
logf("swap: iram restore");
voice_iram_stolen = false;
/* Don't swap trashed data into buffer - _should_ always be the case
if voice_iram_stolen is true since the voice has been swapped in
before hand */
if (my_codec == CODEC_IDX_VOICE)
goto skip_iram_swap;
logf("swap: no swap buffers");
return;
}
my_codec = current_codec;
logf("swapping out codec: %d", my_codec);
/* Invert this when a codec thread enters and leaves */
swap_codec_parity = !swap_codec_parity;
/* If this is true, an odd number of calls has occurred and there's
no codec thread waiting to swap us out when it locks and runs. This
occurs when playback is stopped or when just starting playback and
the audio thread is loading a codec; parities should always be even
on entry when a thread calls this during playback */
if (swap_codec_parity)
{
/* Save our current IRAM and DRAM */
#ifdef IRAM_STEAL
if (voice_iram_stolen)
{
logf("swap: iram restore");
voice_iram_stolen = false;
/* Don't swap trashed data into buffer as the voice IRAM will
already be swapped out - should _always_ be the case if
voice_iram_stolen is true since the voice has been swapped
in beforehand */
if (my_codec == CODEC_IDX_VOICE)
{
logf("voice iram already swapped");
goto skip_iram_swap;
}
}
#endif
memcpy(iram_buf[my_codec], (unsigned char *)CODEC_IRAM_ORIGIN,
CODEC_IRAM_SIZE);
memswap128(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE);
#ifdef IRAM_STEAL
skip_iram_swap:
skip_iram_swap:
#endif
memcpy(dram_buf[my_codec], codecbuf, CODEC_SIZE);
memswap128(dram_buf, codecbuf, CODEC_SIZE);
/* No cache invalidation needed; it will be done in codec_load_ram
or we won't be here otherwise */
}
/* Release my semaphore */
mutex_unlock(&mutex_codecthread);
logf("unlocked: %d", my_codec);
/* Loop until the other codec has locked and run */
do {
@ -923,28 +926,57 @@ skip_iram_swap:
} while (my_codec == current_codec);
/* Wait for other codec to unlock */
/* FIXME: We need some sort of timed boost cancellation here or the CPU
doesn't unboost during playback when the voice codec goes back to
waiting - recall that mutex_lock calls block_thread which is an
indefinite wait that doesn't cancel the thread's CPU boost */
mutex_lock(&mutex_codecthread);
/* Take control */
logf("waiting for lock: %d", my_codec);
set_current_codec(my_codec);
/* Reload our IRAM and DRAM */
memcpy((unsigned char *)CODEC_IRAM_ORIGIN, iram_buf[my_codec],
CODEC_IRAM_SIZE);
memswap128(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE);
memswap128(dram_buf, codecbuf, CODEC_SIZE);
invalidate_icache();
memcpy(codecbuf, dram_buf[my_codec], CODEC_SIZE);
logf("resuming codec:%d", my_codec);
/* Flip parity again */
swap_codec_parity = !swap_codec_parity;
logf("resuming codec: %d", my_codec);
}
/* This function is meant to be used by the buffer stealing functions to
ensure the codec is no longer active and so voice will be swapped-in
before it is called */
static void voice_stop(void)
{
#ifdef PLAYBACK_VOICE
/* Must have a voice codec loaded or we'll hang forever here */
if (!voice_codec_loaded)
return;
LOGFQUEUE("mp3 > voice Q_VOICE_STOP");
queue_post(&voice_queue, Q_VOICE_STOP, 0);
/* Loop until voice empties it's queue, stops and picks up on the new
track; the voice thread must be stopped and waiting for messages
outside the codec */
while (voice_is_playing || !queue_empty(&voice_queue) ||
ci_voice.new_track)
yield();
if (!playing)
pcmbuf_play_stop();
#endif
} /* voice_stop */
#endif /* PLAYBACK_VOICE */
static void set_filebuf_watermark(int seconds)
{
size_t bytes;
if (current_codec == CODEC_IDX_VOICE)
return;
if (!filebuf)
return; /* Audio buffers not yet set up */
@ -1058,6 +1090,14 @@ static void voice_set_offset_callback(size_t value)
(void)value;
}
static void voice_configure_callback(int setting, intptr_t value)
{
if (!dsp_configure(setting, value))
{
logf("Illegal key:%d", setting);
}
}
static size_t voice_filebuf_callback(void *ptr, size_t size)
{
(void)ptr;
@ -1066,6 +1106,34 @@ static size_t voice_filebuf_callback(void *ptr, size_t size)
return 0;
}
/* Handle Q_VOICE_STOP and part of SYS_USB_CONNECTED */
static bool voice_on_voice_stop(bool aborting, size_t *realsize)
{
if (aborting && !playing && pcm_is_playing())
{
/* Aborting: Slight hack - flush PCM buffer if
only being used for voice */
pcmbuf_play_stop();
}
if (voice_is_playing)
{
/* Clear the current buffer */
voice_is_playing = false;
voice_getmore = NULL;
voice_remaining = 0;
voicebuf = NULL;
/* Force the codec to think it's changing tracks */
ci_voice.new_track = 1;
*realsize = 0;
return true; /* Yes, change tracks */
}
return false;
}
static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
{
struct event ev;
@ -1079,15 +1147,21 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
while (1)
{
if (voice_is_playing || playing)
{
queue_wait_w_tmo(&voice_queue, &ev, 0);
if (!voice_is_playing && ev.id == SYS_TIMEOUT)
ev.id = Q_AUDIO_PLAY;
}
else
{
/* We must use queue_wait_w_tmo() because queue_wait() doesn't
unboost the CPU */
queue_wait_w_tmo(&voice_queue, &ev, INT_MAX);
if (!voice_is_playing)
{
if (ev.id == SYS_TIMEOUT)
ev.id = Q_AUDIO_PLAY;
/* FIXME: when long timeouts work correctly max out the the timeout
(we'll still need the timeout guard here) or an infinite timeout
can unboost, use that */
do
queue_wait_w_tmo(&voice_queue, &ev, HZ*5);
while (ev.id == SYS_TIMEOUT); /* Fake infinite wait */
}
switch (ev.id) {
@ -1110,35 +1184,27 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
case Q_VOICE_STOP:
LOGFQUEUE("voice < Q_VOICE_STOP");
if (ev.data == 1 && !playing && pcm_is_playing())
{
/* Aborting: Slight hack - flush PCM buffer if
only being used for voice */
pcmbuf_play_stop();
}
if (voice_is_playing)
{
/* Clear the current buffer */
voice_is_playing = false;
voice_getmore = NULL;
voice_remaining = 0;
voicebuf = NULL;
/* Force the codec to think it's changing tracks */
ci_voice.new_track = 1;
*realsize = 0;
if (voice_on_voice_stop(ev.data, realsize))
return NULL;
}
else
break;
break;
case SYS_USB_CONNECTED:
{
LOGFQUEUE("voice < SYS_USB_CONNECTED");
usb_acknowledge(SYS_USB_CONNECTED_ACK);
bool change_tracks = voice_on_voice_stop(ev.data, realsize);
/* Voice is obviously current so let us swap ourselves away if
playing so audio may stop itself - audio_codec_loaded can
only be true in this case if we're here even if the codec
is only about to load */
if (audio_codec_loaded)
swap_codec();
/* Playback should be finished by now - ack and wait */
usb_acknowledge(SYS_USB_CONNECTED_ACK);
usb_wait_for_disconnect(&voice_queue);
if (change_tracks)
return NULL;
break;
}
case Q_VOICE_PLAY:
LOGFQUEUE("voice < Q_VOICE_PLAY");
@ -1149,17 +1215,17 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
#ifdef IRAM_STEAL
if (voice_iram_stolen)
{
/* Voice is the first to run again and is currently
loaded */
logf("voice: iram restore");
memcpy((void*)CODEC_IRAM_ORIGIN,
iram_buf[CODEC_IDX_VOICE],
CODEC_IRAM_SIZE);
memcpy(CODEC_IRAM_ORIGIN, iram_buf, CODEC_IRAM_SIZE);
voice_iram_stolen = false;
}
#endif
/* must reset the buffer before any playback
begins if needed */
/* Must reset the buffer before any playback begins if
needed */
if (buffer_state == BUFFER_STATE_TRASHED)
audio_reset_buffer(pcmbuf_get_bufsize());
audio_reset_buffer();
voice_is_playing = true;
trigger_cpu_boost();
@ -1168,7 +1234,7 @@ static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize)
voicebuf = voice_data->buf;
voice_getmore = voice_data->callback;
}
goto voice_play_clip;
goto voice_play_clip; /* To exit both switch and while */
case SYS_TIMEOUT:
LOGFQUEUE_SYS_TIMEOUT("voice < SYS_TIMEOUT");
@ -1254,11 +1320,14 @@ static void voice_thread(void)
voice_remaining = 0;
voice_getmore = NULL;
/* FIXME: If we being starting the voice thread without reboot, the
voice_queue could be full of old stuff and we must flush it. */
codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice);
logf("Voice codec finished");
voice_codec_loaded = false;
mutex_unlock(&mutex_codecthread);
voice_thread_p = NULL;
remove_thread(NULL);
} /* voice_thread */
@ -1898,7 +1967,8 @@ static void codec_thread(void)
LOGFQUEUE("codec < Q_CODEC_LOAD_DISK");
audio_codec_loaded = true;
#ifdef PLAYBACK_VOICE
/* Don't sent messages to voice codec if it's not current */
/* Don't sent messages to voice codec if it's already swapped
out or it will never get this */
if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE)
{
LOGFQUEUE("codec > voice Q_AUDIO_PLAY");
@ -2346,7 +2416,7 @@ strip_ape_tag:
/* Skip APE tag */
if (FILEBUFUSED > len)
{
logf("Skipping APE tag (%dB)", len);
logf("Skipping APE tag (%ldB)", len);
buf_widx = RINGBUF_SUB(buf_widx, len);
tracks[track_widx].available -= len;
tracks[track_widx].filesize -= len;
@ -2388,7 +2458,7 @@ static bool audio_read_file(size_t minimum)
if (rc < 0)
{
logf("File ended %dB early", tracks[track_widx].filerem);
logf("File ended %ldB early", tracks[track_widx].filerem);
tracks[track_widx].filesize -= tracks[track_widx].filerem;
tracks[track_widx].filerem = 0;
break;
@ -2408,7 +2478,7 @@ static bool audio_read_file(size_t minimum)
if ((unsigned)rc > tracks[track_widx].filerem)
{
logf("Bad: rc-filerem=%d, fixing", rc-tracks[track_widx].filerem);
logf("Bad: rc-filerem=%ld, fixing", rc-tracks[track_widx].filerem);
tracks[track_widx].filesize += rc - tracks[track_widx].filerem;
tracks[track_widx].filerem = rc;
}
@ -2440,7 +2510,7 @@ static bool audio_read_file(size_t minimum)
if (tracks[track_widx].filerem == 0)
{
logf("Finished buf:%dB", tracks[track_widx].filesize);
logf("Finished buf:%ldB", tracks[track_widx].filesize);
close(current_fd);
current_fd = -1;
audio_strip_tags();
@ -2453,7 +2523,7 @@ static bool audio_read_file(size_t minimum)
}
else
{
logf("%s buf:%dB", ret_val?"Quick":"Partially",
logf("%s buf:%ldB", ret_val?"Quick":"Partially",
tracks[track_widx].filesize - tracks[track_widx].filerem);
return ret_val;
}
@ -2548,7 +2618,7 @@ static bool audio_loadcodec(bool start_play)
tracks[track_widx].has_codec = true;
close(fd);
logf("Done: %dB", size);
logf("Done: %ldB", size);
return true;
}
@ -2901,9 +2971,11 @@ static void audio_fill_file_buffer(
bool had_next_track = audio_next_track() != NULL;
bool continue_buffering;
/* must reset the buffer before use if trashed */
if (buffer_state != BUFFER_STATE_NORMAL)
audio_reset_buffer(pcmbuf_get_bufsize());
/* Must reset the buffer before use if trashed or voice only - voice
file size shouldn't have changed so we can go straight from
BUFFER_STATE_VOICED_ONLY to BUFFER_STATE_INITIALIZED */
if (buffer_state != BUFFER_STATE_INITIALIZED)
audio_reset_buffer();
if (!audio_initialize_buffer_fill(!start_play))
return ;
@ -3342,16 +3414,13 @@ static void audio_initiate_dir_change(long direction)
}
/*
* Layout audio buffer as follows:
* [|TALK]|MALLOC|FILE|GUARD|PCM|AUDIOCODEC|[VOICECODEC|]
* Layout audio buffer as follows - iram buffer depends on target:
* [|SWAP:iram][|TALK]|MALLOC|FILE|GUARD|PCM|[SWAP:dram[|iram]|]
*/
static void audio_reset_buffer(size_t pcmbufsize)
static void audio_reset_buffer(void)
{
/* see audio_get_recording_buffer if this is modified */
size_t offset;
logf("audio_reset_buffer");
logf(" size:%08X", pcmbufsize);
/* If the setup of anything allocated before the file buffer is
changed, do check the adjustments after the buffer_alloc call
@ -3362,19 +3431,34 @@ static void audio_reset_buffer(size_t pcmbufsize)
/* Align the malloc buf to line size. Especially important to cf
targets that do line reads/writes. */
malloc_buf = (unsigned char *)(((uintptr_t)malloc_buf + 15) & ~15);
filebuf = malloc_buf + MALLOC_BUFSIZE;
filebuf = malloc_buf + MALLOC_BUFSIZE; /* filebuf line align implied */
filebuflen = audiobufend - filebuf;
/* Allow for codec(s) at end of audio buffer */
/* Allow for codec swap space at end of audio buffer */
if (talk_voice_required())
{
/* Layout of swap buffer:
* #ifdef IRAM_STEAL (dedicated iram_buf):
* |iram_buf|...audiobuf...|dram_buf|audiobufend
* #else:
* audiobuf...|dram_buf|iram_buf|audiobufend
*/
#ifdef PLAYBACK_VOICE
/* Check for an absolutely nasty situation which should never,
ever happen - frankly should just panic */
if (voice_codec_loaded && current_codec != CODEC_IDX_VOICE)
{
logf("buffer reset with voice swapped");
}
/* line align length which line aligns the calculations below since
all sizes are also at least line aligned - needed for memswap128 */
filebuflen &= ~15;
#ifdef IRAM_STEAL
filebuflen -= CODEC_IRAM_SIZE + 2*CODEC_SIZE;
filebuflen -= CODEC_SIZE;
#else
filebuflen -= 2*(CODEC_IRAM_SIZE + CODEC_SIZE);
filebuflen -= CODEC_SIZE + CODEC_IRAM_SIZE;
#endif
/* Allow 2 codecs at end of audio buffer */
/* Allocate buffers for swapping voice <=> audio */
/* If using IRAM for plugins voice IRAM swap buffer must be dedicated
and out of the way of buffer usage or else a call to audio_get_buffer
and subsequent buffer use might trash the swap space. A plugin
@ -3383,56 +3467,77 @@ static void audio_reset_buffer(size_t pcmbufsize)
has been obtained already or never allowing use of the voice IRAM
buffer within the audio buffer. Using buffer_alloc basically
implements the second in a more convenient way. */
iram_buf[CODEC_IDX_AUDIO] = filebuf + filebuflen;
dram_buf[CODEC_IDX_AUDIO] = iram_buf[CODEC_IDX_AUDIO] + CODEC_IRAM_SIZE;
dram_buf = filebuf + filebuflen;
#ifdef IRAM_STEAL
/* Allocate voice IRAM swap buffer once */
if (iram_buf[CODEC_IDX_VOICE] == NULL)
if (iram_buf == NULL)
{
iram_buf[CODEC_IDX_VOICE] = buffer_alloc(CODEC_IRAM_SIZE);
iram_buf = buffer_alloc(CODEC_IRAM_SIZE);
/* buffer_alloc moves audiobuf; this is safe because only the end
* has been touched so far in this function and the address of
* filebuf + filebuflen is not changed */
malloc_buf += CODEC_IRAM_SIZE;
filebuf += CODEC_IRAM_SIZE;
filebuf += CODEC_IRAM_SIZE;
filebuflen -= CODEC_IRAM_SIZE;
}
dram_buf[CODEC_IDX_VOICE] = dram_buf[CODEC_IDX_AUDIO] + CODEC_SIZE;
#else
iram_buf[CODEC_IDX_VOICE] = dram_buf[CODEC_IDX_AUDIO] + CODEC_SIZE;
dram_buf[CODEC_IDX_VOICE] = iram_buf[CODEC_IDX_VOICE] + CODEC_IRAM_SIZE;
/* Allocate iram_buf after dram_buf */
iram_buf = dram_buf + CODEC_SIZE;
#endif /* IRAM_STEAL */
#endif /* PLAYBACK_VOICE */
}
else
{
#ifdef PLAYBACK_VOICE
/* Allow for 1 codec at end of audio buffer */
filebuflen -= CODEC_IRAM_SIZE + CODEC_SIZE;
iram_buf[CODEC_IDX_AUDIO] = filebuf + filebuflen;
dram_buf[CODEC_IDX_AUDIO] = iram_buf[CODEC_IDX_AUDIO] + CODEC_IRAM_SIZE;
iram_buf[CODEC_IDX_VOICE] = NULL;
dram_buf[CODEC_IDX_VOICE] = NULL;
/* No swap buffers needed */
iram_buf = NULL;
dram_buf = NULL;
#endif
}
filebuflen -= pcmbuf_init(pcmbufsize, filebuf + filebuflen) + GUARD_BUFSIZE;
/* Subtract whatever the pcm buffer says it used plus the guard buffer */
filebuflen -= pcmbuf_init(filebuf + filebuflen) + GUARD_BUFSIZE;
/* Ensure that file buffer is aligned */
offset = -(size_t)filebuf & 3;
filebuf += offset;
filebuflen -= offset;
/* Make sure filebuflen is a longword multiple after adjustment - filebuf
will already be line aligned */
filebuflen &= ~3;
/* Set the high watermark as 75% full...or 25% empty :) */
#if MEM > 8
high_watermark = (3*filebuflen)/4;
high_watermark = 3*filebuflen / 4;
#endif
/* Clear any references to the file buffer */
buffer_state = BUFFER_STATE_NORMAL;
buffer_state = BUFFER_STATE_INITIALIZED;
#ifdef ROCKBOX_HAS_LOGF
/* Make sure everything adds up - yes, some info is a bit redundant but
aids viewing and the sumation of certain variables should add up to
the location of others. */
{
size_t pcmbufsize;
unsigned char * pcmbuf = pcmbuf_get_meminfo(&pcmbufsize);
logf("mabuf: %08X", (unsigned)malloc_buf);
logf("mabufe: %08X", (unsigned)(malloc_buf + MALLOC_BUFSIZE));
logf("fbuf: %08X", (unsigned)filebuf);
logf("fbufe: %08X", (unsigned)(filebuf + filebuflen));
logf("gbuf: %08X", (unsigned)(filebuf + filebuflen));
logf("gbufe: %08X", (unsigned)(filebuf + filebuflen + GUARD_BUFSIZE));
logf("pcmb: %08X", (unsigned)pcmbuf);
logf("pcmbe: %08X", (unsigned)(pcmbuf + pcmbufsize));
if (dram_buf)
{
logf("dramb: %08X", (unsigned)dram_buf);
logf("drambe: %08X", (unsigned)(dram_buf + CODEC_SIZE));
}
if (iram_buf)
{
logf("iramb: %08X", (unsigned)iram_buf);
logf("irambe: %08X", (unsigned)(iram_buf + CODEC_IRAM_SIZE));
}
}
#endif
}
#if MEM > 8
@ -3454,6 +3559,16 @@ static void audio_thread(void)
#ifdef PLAYBACK_VOICE
/* Unlock mutex that init stage locks before creating this thread */
mutex_unlock(&mutex_codecthread);
/* Buffers must be set up by now - should panic - really */
if (buffer_state != BUFFER_STATE_INITIALIZED)
{
logf("audio_thread start: no buffer");
}
/* Have to wait for voice to load up or else the codec swap will be
invalid when an audio codec is loaded */
wait_for_voice_swap_in();
#endif
while (1)
@ -3604,12 +3719,14 @@ void audio_init(void)
static struct mp3entry id3_voice;
#endif
logf("audio: %s", audio_is_initialized ?
"initializing" : "already initialized");
/* Can never do this twice */
if (audio_is_initialized)
{
logf("audio: already initialized");
return;
}
logf("audio: initializing");
/* Initialize queues before giving control elsewhere in case it likes
to send messages. Thread creation will be delayed however so nothing
@ -3620,6 +3737,7 @@ void audio_init(void)
hardware is initialized - audio thread unlocks it after final init
stage */
mutex_lock(&mutex_codecthread);
queue_init(&voice_queue, true);
#endif
queue_init(&audio_queue, true);
queue_enable_queue_send(&audio_queue, &audio_queue_sender_list);
@ -3663,6 +3781,7 @@ void audio_init(void)
ci_voice.seek_complete = voice_do_nothing;
ci_voice.set_elapsed = voice_set_elapsed_callback;
ci_voice.set_offset = voice_set_offset_callback;
ci_voice.configure = voice_configure_callback;
ci_voice.discard_codec = voice_do_nothing;
ci_voice.taginfo_ready = &voicetagtrue;
ci_voice.id3 = &id3_voice;
@ -3671,12 +3790,14 @@ void audio_init(void)
#endif
/* initialize the buffer */
filebuf = audiobuf; /* must be non-NULL for audio_set_crossfade */
filebuf = audiobuf;
/* audio_reset_buffer must to know the size of voice buffer so init
voice first */
talk first */
talk_init();
/* Create the threads late now that we shouldn't be yielding again before
returning */
codec_thread_p = create_thread(
codec_thread, codec_stack, sizeof(codec_stack),
codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)
@ -3686,8 +3807,24 @@ void audio_init(void)
audio_thread_name IF_PRIO(, PRIORITY_BUFFERING)
IF_COP(, CPU, false));
audio_set_crossfade(global_settings.crossfade);
#ifdef PLAYBACK_VOICE
/* TODO: Change this around when various speech codecs can be used */
if (talk_voice_required())
{
logf("Starting voice codec");
create_thread(voice_thread, voice_stack,
sizeof(voice_stack), voice_thread_name
IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU, false));
}
#endif
/* Set crossfade setting for next buffer init which should be about... */
pcmbuf_crossfade_enable(global_settings.crossfade);
/* ...now! Set up the buffers */
audio_reset_buffer();
/* Probably safe to say */
audio_is_initialized = true;
sound_settings_apply();

View file

@ -64,8 +64,6 @@ void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3,
bool last_track));
void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3,
bool last_track));
void voice_init(void);
void voice_stop(void);
#if CONFIG_CODEC == SWCODEC /* This #ifdef is better here than gui/gwps.c */
extern void audio_next_dir(void);

View file

@ -528,9 +528,6 @@ void talk_init(void)
if (has_voicefile)
{
voicefile_size = filesize(filehandle);
#if CONFIG_CODEC == SWCODEC
voice_init();
#endif
close(filehandle); /* close again, this was just to detect presence */
filehandle = -1;
}

View file

@ -248,6 +248,8 @@ target/coldfire/crt0.S
target/coldfire/memcpy-coldfire.S
target/coldfire/memmove-coldfire.S
target/coldfire/memset-coldfire.S
target/coldfire/memswap128-coldfire.S
common/memswap128.c
#if defined(HAVE_LCD_COLOR) \
|| defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED)
target/coldfire/memset16-coldfire.S
@ -269,6 +271,7 @@ common/strlen.c
#ifndef SIMULATOR
target/arm/memset-arm.S
target/arm/memset16-arm.S
target/arm/memswap128-arm.S
#if CONFIG_I2C == I2C_PP5020 || CONFIG_I2C == I2C_PP5002
target/arm/i2c-pp.c
#elif CONFIG_I2C == I2C_PNX0101
@ -295,6 +298,7 @@ common/memcpy.c
common/memmove.c
common/memset.c
common/memset16.c
common/memswap128.c
common/strlen.c
#ifndef SIMULATOR
crt0.S

View file

@ -0,0 +1,44 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 by Michael Sevakis
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include <string.h>
#include <inttypes.h>
void memswap128(void *a, void *b, size_t len)
{
for (len >>= 4; len > 0; len--, a += 16, b += 16)
{
int32_t a0 = *((int32_t *)a + 0);
int32_t a1 = *((int32_t *)a + 1);
int32_t a2 = *((int32_t *)a + 2);
int32_t a3 = *((int32_t *)a + 3);
int32_t b0 = *((int32_t *)b + 0);
int32_t b1 = *((int32_t *)b + 1);
int32_t b2 = *((int32_t *)b + 2);
int32_t b3 = *((int32_t *)b + 3);
*((int32_t *)b + 0) = a0;
*((int32_t *)b + 1) = a1;
*((int32_t *)b + 2) = a2;
*((int32_t *)b + 3) = a3;
*((int32_t *)a + 0) = b0;
*((int32_t *)a + 1) = b1;
*((int32_t *)a + 2) = b2;
*((int32_t *)a + 3) = b3;
}
}

View file

@ -24,4 +24,15 @@
void memset16(void *dst, int val, size_t len);
/**
* memswap128
*
* Exchanges the contents of two buffers.
* For maximum efficiency, this function performs no aligning of addresses
* and buf1, buf2 and len should be 16 byte (128 bit) aligned. Not being at
* least longword aligned will fail on some architechtures. Any len mod 16
* at the end is not swapped.
*/
void memswap128(void *buf1, void *buf2, size_t len);
#endif /* _MEMORY_H_ */

View file

@ -0,0 +1,44 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 by Michael Sevakis
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/****************************************************************************
* void memswap128(void *buf1, void *buf2, size_t len)
*/
.section .icode, "ax", %progbits
.align 2
.global memswap128
.type memswap128, %function
memswap128:
@ r0 = buf1
@ r1 = buf2
@ r2 = len
movs r2, r2, lsr #4 @ bytes => lines, len == 0?
moveq pc, lr @ not at least a line? leave
stmdb sp!, { r4-r10, lr } @ save registers and return address
.loop: @
ldmia r0, { r3-r6 } @ read four longwords from buf1
ldmia r1, { r7-r10 } @ read four longwords from buf2
stmia r0!, { r7-r10 } @ write buf2 data to buf1, buf1 += 16
stmia r1!, { r3-r6 } @ write buf1 data to buf2, buf2 += 16
subs r2, r2, #1 @ len -= 1, len > 0 ?
bhi .loop @ yes? keep exchanging
ldmia sp!, { r4-r10, pc } @ restore registers and return
.end:
.size memswap128, .end-memswap128

View file

@ -0,0 +1,50 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 by Michael Sevakis
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/****************************************************************************
* void memswap128(void *buf1, void *buf2, size_t len)
*/
.section .icode, "ax", @progbits
.align 2
.global memswap128
.type memswap128, @function
memswap128:
lea.l -28(%sp), %sp | save registers
movem.l %d2-%d7/%a2, (%sp) |
movem.l 32(%sp), %a0-%a2 | %a0 = buf1
| %a1 = buf2
| %a2 = len
lea.l -15(%a0, %a2.l), %a2 | %a2 = end address - 15
cmp.l %a0, %a2 | end address <= buf1?
bls.b .no_lines | not at least a line? leave
.loop: |
movem.l (%a0), %d0-%d3 | read four longwords from buf1
movem.l (%a1), %d4-%d7 | read four longwords from buf2
movem.l %d4-%d7, (%a0) | write buf2 data to buf1
movem.l %d0-%d3, (%a1) | write buf1 data to buf2
lea.l 16(%a0), %a0 | buf1 += 16
lea.l 16(%a1), %a1 | buf2 += 16
cmp.l %a0, %a2 | %a0 < %d0?
bhi.b .loop | yes? keep exchanging
.no_lines: |
movem.l (%sp), %d2-%d7/%a2 | restore registers
lea.l 28(%sp), %sp |
rts |
.end:
.size memswap128, .end-memswap128