Changeset View
Changeset View
Standalone View
Standalone View
sys/arm/broadcom/bcm2835/bcm2835_audio.c
| Show First 20 Lines • Show All 107 Lines • ▼ Show 20 Lines | struct bcm2835_audio_chinfo { | ||||
| */ | */ | ||||
| int available_space; | int available_space; | ||||
| int playback_state; | int playback_state; | ||||
| uint64_t callbacks; | uint64_t callbacks; | ||||
| uint64_t submitted_samples; | uint64_t submitted_samples; | ||||
| uint64_t retrieved_samples; | uint64_t retrieved_samples; | ||||
| uint64_t underruns; | uint64_t underruns; | ||||
| int starved; | int starved; | ||||
| struct bcm_log_vars { | |||||
| unsigned int bsize ; | |||||
| int slept_for_lack_of_space ; | |||||
| } log_vars; | |||||
| #define DEFAULT_LOG_VALUES \ | |||||
| ((struct bcm_log_vars) { .bsize = 0 , .slept_for_lack_of_space = 0 }) | |||||
| }; | }; | ||||
| struct bcm2835_audio_info { | struct bcm2835_audio_info { | ||||
| device_t dev; | device_t dev; | ||||
| unsigned int bufsz; | unsigned int bufsz; | ||||
| struct bcm2835_audio_chinfo pch; | struct bcm2835_audio_chinfo pch; | ||||
| uint32_t dest, volume; | uint32_t dest, volume; | ||||
| struct intr_config_hook intr_hook; | struct intr_config_hook intr_hook; | ||||
| Show All 39 Lines | |||||
| /* things that are expected to (will) clobber the output */ | /* things that are expected to (will) clobber the output */ | ||||
| #define TRACE(sc,...) \ | #define TRACE(sc,...) \ | ||||
| do { \ | do { \ | ||||
| if(sc->verbose_trace > 2) \ | if(sc->verbose_trace > 2) \ | ||||
| device_printf((sc)->dev, __VA_ARGS__); \ | device_printf((sc)->dev, __VA_ARGS__); \ | ||||
| } while(0) | } while(0) | ||||
| /* Useful for circular buffer calcs */ | |||||
| #define MOD_DIFF(front,rear,mod) (((mod) + (front) - (rear)) % (mod)) | |||||
| static const char * | static const char * | ||||
| dest_description(uint32_t dest) | dest_description(uint32_t dest) | ||||
| { | { | ||||
| switch (dest) { | switch (dest) { | ||||
| case DEST_AUTO: | case DEST_AUTO: | ||||
| return "AUTO"; | return "AUTO"; | ||||
| break; | break; | ||||
| ▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | if (status != 0) | ||||
| return; | return; | ||||
| if (m.type == VC_AUDIO_MSG_TYPE_RESULT) { | if (m.type == VC_AUDIO_MSG_TYPE_RESULT) { | ||||
| if (m.u.result.success) { | if (m.u.result.success) { | ||||
| device_printf(sc->dev, | device_printf(sc->dev, | ||||
| "msg type %08x failed\n", | "msg type %08x failed\n", | ||||
| m.type); | m.type); | ||||
| } | } | ||||
| } else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) { | } else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) { | ||||
| struct bcm2835_audio_chinfo *ch = m.u.complete.cookie; | unsigned int signaled = 0; | ||||
| struct bcm2835_audio_chinfo *ch ; | |||||
| #if defined(__aarch64__) | |||||
| ch = (void *) ((((size_t)m.u.complete.callback) << 32) | |||||
| | ((size_t)m.u.complete.cookie)); | |||||
| #else | |||||
| ch = (void *) (m.u.complete.cookie); | |||||
| #endif | |||||
| int count = m.u.complete.count & 0xffff; | int count = m.u.complete.count & 0xffff; | ||||
| int perr = (m.u.complete.count & (1U << 30)) != 0; | int perr = (m.u.complete.count & (1U << 30)) != 0; | ||||
| TRACE(sc,"in:: count:0x%x perr:%d\n",m.u.complete.count,perr); | |||||
| ch->callbacks++; | ch->callbacks++; | ||||
| if (perr) | if (perr) | ||||
| ch->underruns++; | ch->underruns++; | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| if (ch->playback_state != PLAYBACK_IDLE) { | if (ch->playback_state != PLAYBACK_IDLE) { | ||||
| /* Prevent LOR */ | /* Prevent LOR */ | ||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| chn_intr(sc->pch.channel); | chn_intr(sc->pch.channel); | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| } | } | ||||
| /* We should check again, state might have changed */ | /* We should check again, state might have changed */ | ||||
| if (ch->playback_state != PLAYBACK_IDLE) { | if (ch->playback_state != PLAYBACK_IDLE) { | ||||
| if (!perr) { | if (!perr) { | ||||
| if ((ch->available_space + count)> VCHIQ_AUDIO_BUFFER_SIZE) { | if ((ch->available_space + count)> VCHIQ_AUDIO_BUFFER_SIZE) { | ||||
| device_printf(sc->dev, "inconsistent data in callback:\n"); | device_printf(sc->dev, "inconsistent data in callback:\n"); | ||||
| device_printf(sc->dev, "available_space == %d, count = %d, perr=%d\n", | device_printf(sc->dev, "available_space == %d, count = %d, perr=%d\n", | ||||
| ch->available_space, count, perr); | ch->available_space, count, perr); | ||||
| device_printf(sc->dev, | device_printf(sc->dev, | ||||
| "retrieved_samples = %ju, submitted_samples = %ju\n", | "retrieved_samples = %ju, submitted_samples = %ju\n", | ||||
| (uintmax_t) ch->retrieved_samples, (uintmax_t) ch->submitted_samples); | (uintmax_t) ch->retrieved_samples, (uintmax_t) ch->submitted_samples); | ||||
| } | } | ||||
| } | |||||
| ch->available_space += count; | ch->available_space += count; | ||||
| ch->retrieved_samples += count; | ch->retrieved_samples += count; | ||||
| /* | |||||
| * XXXMDC | |||||
| * Experimental: if VC says it's empty, believe it | |||||
| * Has to come after the usual adjustments | |||||
| */ | |||||
| if(perr){ | |||||
| ch->available_space = VCHIQ_AUDIO_BUFFER_SIZE; | |||||
| perr = ch->retrieved_samples; // shd be != 0 | |||||
| } | } | ||||
| if (perr || (ch->available_space >= VCHIQ_AUDIO_PACKET_SIZE)) | |||||
| if ((ch->available_space >= 1*VCHIQ_AUDIO_PACKET_SIZE)){ | |||||
| cv_signal(&sc->worker_cv); | cv_signal(&sc->worker_cv); | ||||
| signaled = 1; | |||||
| } | } | ||||
| } | |||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| if(perr){ | |||||
| WARN_THAT(sc, | |||||
| "VC starved; reported %u for a total of %u\n" | |||||
| "worker %s\n" , | |||||
| count,perr, | |||||
| (signaled ? "signaled": "not signaled") | |||||
| ); | |||||
| } | |||||
| } else | } else | ||||
| WARN_THAT(sc, "%s: unknown m.type: %d\n", __func__, m.type); | WARN_THAT(sc, "%s: unknown m.type: %d\n", __func__, m.type); | ||||
| } | } | ||||
| /* VCHIQ stuff */ | /* VCHIQ stuff */ | ||||
| static void | static void | ||||
| bcm2835_audio_init(struct bcm2835_audio_info *sc) | bcm2835_audio_init(struct bcm2835_audio_info *sc) | ||||
| { | { | ||||
| ▲ Show 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | bcm2835_audio_stop(struct bcm2835_audio_chinfo *ch) | ||||
| VC_AUDIO_MSG_T m; | VC_AUDIO_MSG_T m; | ||||
| int ret; | int ret; | ||||
| struct bcm2835_audio_info *sc = ch->parent; | struct bcm2835_audio_info *sc = ch->parent; | ||||
| if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { | if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { | ||||
| m.type = VC_AUDIO_MSG_TYPE_STOP; | m.type = VC_AUDIO_MSG_TYPE_STOP; | ||||
| m.u.stop.draining = 0; | m.u.stop.draining = 0; | ||||
| INFORM_THAT(sc,"sending stop\n"); | |||||
| ret = vchi_msg_queue(sc->vchi_handle, | ret = vchi_msg_queue(sc->vchi_handle, | ||||
| &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); | &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); | ||||
| if (ret != 0) | if (ret != 0) | ||||
| REPORT_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", | REPORT_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", | ||||
| __func__, ret); | __func__, ret); | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | if (ret != 0) | ||||
| __func__, ret); | __func__, ret); | ||||
| } | } | ||||
| } | } | ||||
| static bool | static bool | ||||
| bcm2835_audio_buffer_should_sleep(struct bcm2835_audio_chinfo *ch) | bcm2835_audio_buffer_should_sleep(struct bcm2835_audio_chinfo *ch) | ||||
| { | { | ||||
| ch->log_vars.slept_for_lack_of_space = 0; | |||||
| if (ch->playback_state != PLAYBACK_PLAYING) | if (ch->playback_state != PLAYBACK_PLAYING) | ||||
| return (true); | return (true); | ||||
| /* Not enough data */ | /* Not enough data */ | ||||
| if (sndbuf_getready(ch->buffer) < VCHIQ_AUDIO_PACKET_SIZE) { | /* XXXMDC Take unsubmitted stuff into account */ | ||||
| printf("starve\n"); | if (sndbuf_getready(ch->buffer) | ||||
| - MOD_DIFF( | |||||
| ch->unsubmittedptr, | |||||
| sndbuf_getreadyptr(ch->buffer), | |||||
| sndbuf_getsize(ch->buffer) | |||||
| ) < VCHIQ_AUDIO_PACKET_SIZE) { | |||||
| ch->starved++; | ch->starved++; | ||||
| return (true); | return (true); | ||||
| } | } | ||||
| /* Not enough free space */ | /* Not enough free space */ | ||||
| if (ch->available_space < VCHIQ_AUDIO_PACKET_SIZE) { | if (ch->available_space < VCHIQ_AUDIO_PACKET_SIZE) { | ||||
| ch->log_vars.slept_for_lack_of_space = 1; | |||||
| return (true); | return (true); | ||||
| } | } | ||||
| return (false); | return (false); | ||||
| } | } | ||||
| static void | static void | ||||
| bcm2835_audio_write_samples(struct bcm2835_audio_chinfo *ch, void *buf, uint32_t count) | bcm2835_audio_write_samples(struct bcm2835_audio_chinfo *ch, void *buf, uint32_t count) | ||||
| { | { | ||||
| struct bcm2835_audio_info *sc = ch->parent; | struct bcm2835_audio_info *sc = ch->parent; | ||||
| VC_AUDIO_MSG_T m; | VC_AUDIO_MSG_T m; | ||||
| int ret; | int ret; | ||||
| if (sc->vchi_handle == VCHIQ_SERVICE_HANDLE_INVALID) { | if (sc->vchi_handle == VCHIQ_SERVICE_HANDLE_INVALID) { | ||||
| return; | return; | ||||
| } | } | ||||
| m.type = VC_AUDIO_MSG_TYPE_WRITE; | m.type = VC_AUDIO_MSG_TYPE_WRITE; | ||||
| m.u.write.count = count; | m.u.write.count = count; | ||||
| m.u.write.max_packet = VCHIQ_AUDIO_PACKET_SIZE; | m.u.write.max_packet = VCHIQ_AUDIO_PACKET_SIZE; | ||||
| m.u.write.callback = NULL; | #if defined(__aarch64__) | ||||
| m.u.write.cookie = ch; | m.u.write.callback = (uint32_t)(((size_t) ch) >> 32) & 0xffffffff; | ||||
| m.u.write.cookie = (uint32_t)(((size_t) ch) & 0xffffffff); | |||||
| #else | |||||
| m.u.write.callback = (uint32_t) NULL; | |||||
| m.u.write.cookie = (uint32_t) ch; | |||||
| #endif | |||||
| m.u.write.silence = 0; | m.u.write.silence = 0; | ||||
| ret = vchi_msg_queue(sc->vchi_handle, | ret = vchi_msg_queue(sc->vchi_handle, | ||||
| &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); | &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); | ||||
| if (ret != 0) | if (ret != 0) | ||||
| REPORT_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", | REPORT_ERROR(sc, "%s: vchi_msg_queue failed (err %d)\n", | ||||
| __func__, ret); | __func__, ret); | ||||
| Show All 30 Lines | while (1) { | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| /* | /* | ||||
| * wait until there are flags set or buffer is ready | * wait until there are flags set or buffer is ready | ||||
| * to consume more samples | * to consume more samples | ||||
| */ | */ | ||||
| while ((sc->flags_pending == 0) && | while ((sc->flags_pending == 0) && | ||||
| bcm2835_audio_buffer_should_sleep(ch)) { | bcm2835_audio_buffer_should_sleep(ch)) { | ||||
| cv_wait_sig(&sc->worker_cv, &sc->lock); | cv_wait_sig(&sc->worker_cv, &sc->lock); | ||||
| if((sc-> flags_pending == 0) | |||||
| && ch->log_vars.slept_for_lack_of_space) { | |||||
| TRACE(sc,"slept for lack of space\n"); | |||||
| } | } | ||||
| } | |||||
| flags = sc->flags_pending; | flags = sc->flags_pending; | ||||
| /* Clear pending flags */ | /* Clear pending flags */ | ||||
| sc->flags_pending = 0; | sc->flags_pending = 0; | ||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| /* Requested to change parameters */ | /* Requested to change parameters */ | ||||
| if (flags & AUDIO_PARAMS) { | if (flags & AUDIO_PARAMS) { | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| Show All 9 Lines | while (1) { | ||||
| /* Requested to stop playback */ | /* Requested to stop playback */ | ||||
| if ((flags & AUDIO_STOP) && | if ((flags & AUDIO_STOP) && | ||||
| (ch->playback_state == PLAYBACK_PLAYING)) { | (ch->playback_state == PLAYBACK_PLAYING)) { | ||||
| bcm2835_audio_stop(ch); | bcm2835_audio_stop(ch); | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| bcm2835_audio_reset_channel(&sc->pch); | bcm2835_audio_reset_channel(&sc->pch); | ||||
| ch->playback_state = PLAYBACK_IDLE; | ch->playback_state = PLAYBACK_IDLE; | ||||
| long sub_total = ch->submitted_samples; | |||||
| long retd = ch->retrieved_samples; | |||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| INFORM_THAT(sc, | |||||
| "stopped audio. submitted a total of %lu " | |||||
| "having been acked %lu\n", | |||||
| sub_total, retd | |||||
| ); | |||||
| continue; | continue; | ||||
| } | } | ||||
| /* Requested to start playback */ | /* Requested to start playback */ | ||||
| if ((flags & AUDIO_PLAY) && | if ((flags & AUDIO_PLAY) && | ||||
| (ch->playback_state == PLAYBACK_IDLE)) { | (ch->playback_state == PLAYBACK_IDLE)) { | ||||
| INFORM_THAT(sc, | |||||
| "starting audio\n" | |||||
| ); | |||||
| unsigned int bsize = sndbuf_getsize(ch->buffer); | |||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| ch->playback_state = PLAYBACK_PLAYING; | ch->playback_state = PLAYBACK_PLAYING; | ||||
| ch->log_vars.bsize = bsize; | |||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| INFORM_THAT(sc, | |||||
| "buffer size is %u\n", | |||||
| bsize | |||||
| ); | |||||
| bcm2835_audio_start(ch); | bcm2835_audio_start(ch); | ||||
| } | } | ||||
| if (ch->playback_state == PLAYBACK_IDLE) | if (ch->playback_state == PLAYBACK_IDLE) | ||||
| continue; | continue; | ||||
| if (sndbuf_getready(ch->buffer) == 0) | if (sndbuf_getready(ch->buffer) == 0) | ||||
| continue; | continue; | ||||
| uint32_t i_count; | |||||
| count = sndbuf_getready(ch->buffer); | /* XXXMDC Take unsubmitted stuff into account */ | ||||
| count | |||||
| = i_count | |||||
| = sndbuf_getready(ch->buffer) | |||||
| - MOD_DIFF( | |||||
| ch->unsubmittedptr, | |||||
| sndbuf_getreadyptr(ch->buffer), | |||||
| sndbuf_getsize(ch->buffer) | |||||
| ); | |||||
| size = sndbuf_getsize(ch->buffer); | size = sndbuf_getsize(ch->buffer); | ||||
| readyptr = sndbuf_getreadyptr(ch->buffer); | readyptr = ch->unsubmittedptr; | ||||
| int size_changed=0; | |||||
| unsigned int available; | |||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| if (readyptr + count > size) | if(size != ch->log_vars.bsize){ | ||||
| ch->log_vars.bsize = size; | |||||
| size_changed = 1; | |||||
| } | |||||
| available = ch->available_space; | |||||
| /* | |||||
| * XXXMDC | |||||
| * | |||||
| * On arm64, got into situations where | |||||
| * readyptr was less than a packet away | |||||
| * from the end of the buffer, which led | |||||
| * to count being set to 0 and, inexorably, starvation. | |||||
| * Code below tries to take that into account. | |||||
| * The problem might have been fixed with some of the | |||||
| * other changes that were made in the meantime, | |||||
| * but for now this works fine. | |||||
| */ | |||||
| if (readyptr + count > size){ | |||||
| count = size - readyptr; | count = size - readyptr; | ||||
| count = min(count, ch->available_space); | } | ||||
| if(count > ch->available_space){ | |||||
| count = ch->available_space; | |||||
| count -= (count % VCHIQ_AUDIO_PACKET_SIZE); | count -= (count % VCHIQ_AUDIO_PACKET_SIZE); | ||||
| }else if (count > VCHIQ_AUDIO_PACKET_SIZE){ | |||||
| count -= (count % VCHIQ_AUDIO_PACKET_SIZE); | |||||
| }else if (size > count + readyptr) { | |||||
| count = 0; | |||||
| } | |||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| if(count % VCHIQ_AUDIO_PACKET_SIZE != 0){ | |||||
| WARN_THAT(sc, | |||||
| "count: %u initial count: %u " | |||||
| "size: %u readyptr: %u available: %u" | |||||
| "\n", | |||||
| count,i_count,size,readyptr, available); | |||||
| } | |||||
| if(size_changed) INFORM_THAT(sc,"bsize changed to %u\n",size); | |||||
| if (count < VCHIQ_AUDIO_PACKET_SIZE) | if (count == 0){ | ||||
| WARN_THAT(sc, | |||||
| "not enough room for a packet: count %d," | |||||
| " i_count %d, rptr %d, size %d\n", | |||||
| count, i_count, readyptr, size | |||||
| ); | |||||
| continue; | continue; | ||||
| } | |||||
| buf = (uint8_t*)sndbuf_getbuf(ch->buffer) + readyptr; | buf = (uint8_t*)sndbuf_getbuf(ch->buffer) + readyptr; | ||||
| bcm2835_audio_write_samples(ch, buf, count); | bcm2835_audio_write_samples(ch, buf, count); | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| ch->unsubmittedptr = (ch->unsubmittedptr + count) % sndbuf_getsize(ch->buffer); | ch->unsubmittedptr = (ch->unsubmittedptr + count) % sndbuf_getsize(ch->buffer); | ||||
| ch->available_space -= count; | ch->available_space -= count; | ||||
| ch->submitted_samples += count; | ch->submitted_samples += count; | ||||
| long sub = count; | |||||
| long sub_total = ch->submitted_samples; | |||||
| long retd = ch->retrieved_samples; | |||||
| KASSERT(ch->available_space >= 0, ("ch->available_space == %d\n", ch->available_space)); | KASSERT(ch->available_space >= 0, ("ch->available_space == %d\n", ch->available_space)); | ||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| TRACE(sc, | |||||
| "submitted %lu for a total of %lu having been acked %lu; " | |||||
| "rptr %d, had %u available \n", | |||||
| sub, sub_total, retd, readyptr, available); | |||||
| } | } | ||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| sc->worker_state = WORKER_STOPPED; | sc->worker_state = WORKER_STOPPED; | ||||
| cv_signal(&sc->worker_cv); | cv_signal(&sc->worker_cv); | ||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| kproc_exit(0); | kproc_exit(0); | ||||
| Show All 34 Lines | bcmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) | ||||
| buffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO); | buffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO); | ||||
| if (sndbuf_setup(ch->buffer, buffer, sc->bufsz) != 0) { | if (sndbuf_setup(ch->buffer, buffer, sc->bufsz) != 0) { | ||||
| device_printf(sc->dev, "sndbuf_setup failed\n"); | device_printf(sc->dev, "sndbuf_setup failed\n"); | ||||
| free(buffer, M_DEVBUF); | free(buffer, M_DEVBUF); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| ch->log_vars = DEFAULT_LOG_VALUES; | |||||
| BCM2835_AUDIO_LOCK(sc); | BCM2835_AUDIO_LOCK(sc); | ||||
| bcm2835_worker_update_params(sc); | bcm2835_worker_update_params(sc); | ||||
| BCM2835_AUDIO_UNLOCK(sc); | BCM2835_AUDIO_UNLOCK(sc); | ||||
| return ch; | return ch; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 352 Lines • Show Last 20 Lines | |||||