diff options
-rw-r--r-- | common/audio_codec.c | 10 | ||||
-rw-r--r-- | common/audio_codec_wov.c | 404 | ||||
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | include/audio_codec.h | 75 | ||||
-rw-r--r-- | include/config.h | 15 | ||||
-rw-r--r-- | include/ec_commands.h | 69 |
6 files changed, 573 insertions, 1 deletions
diff --git a/common/audio_codec.c b/common/audio_codec.c index 1dcc7e9b16..e21df5c039 100644 --- a/common/audio_codec.c +++ b/common/audio_codec.c @@ -10,7 +10,15 @@ #define CPRINTS(format, args...) cprints(CC_AUDIO_CODEC, format, ## args) -static const uint32_t capabilities; +static const uint32_t capabilities = + 0 +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_AUDIO_SHM + | BIT(EC_CODEC_CAP_WOV_AUDIO_SHM) +#endif +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_LANG_SHM + | BIT(EC_CODEC_CAP_WOV_LANG_SHM) +#endif + ; static struct { uint8_t cap; diff --git a/common/audio_codec_wov.c b/common/audio_codec_wov.c new file mode 100644 index 0000000000..3609967d9d --- /dev/null +++ b/common/audio_codec_wov.c @@ -0,0 +1,404 @@ +/* + * Copyright 2019 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "audio_codec.h" +#include "console.h" +#include "host_command.h" +#include "sha256.h" +#include "system.h" +#include "task.h" +#include "util.h" + +#define CPRINTS(format, args...) cprints(CC_AUDIO_CODEC, format, ## args) + +/* + * To shorten the variable names, or the following code is likely to greater + * than 80 columns. + */ +#define AUDIO_BUF_LEN CONFIG_AUDIO_CODEC_WOV_AUDIO_BUF_LEN +#define LANG_BUF_LEN CONFIG_AUDIO_CODEC_WOV_LANG_BUF_LEN + +static uint8_t lang_hash[SHA256_DIGEST_SIZE]; +static uint32_t lang_len; + +/* + * The variables below are shared between host command and WoV task. This lock + * is designed to protect them. + */ +static struct mutex lock; + +/* + * wov_enabled is shared. + * + * host command task: + * - is the only writer + * - no need to lock if read + */ +static uint8_t wov_enabled; + +/* + * hotword_detected is shared. + */ +static uint8_t hotword_detected; + +/* + * audio_buf_rp and audio_buf_wp are shared. + * + * Note that: sample width is 16-bit. + * + * Typical ring-buffer implementation: + * If audio_buf_rp == audio_buf_wp, empty. + * If (audio_buf_wp + 2) % buf_len == audio_buf_rp, full. + */ +static uint32_t audio_buf_rp, audio_buf_wp; + +static int is_buf_full(void) +{ + return ((audio_buf_wp + 2) % AUDIO_BUF_LEN) == audio_buf_rp; +} + +static int check_lang_buf(uint8_t *data, uint32_t len, const uint8_t *hash) +{ + /* + * Note: sizeof(struct sha256_ctx) = 200 bytes + * should put into .bss, or stack is likely to overflow (~640 bytes) + */ + static struct sha256_ctx ctx; + uint8_t *digest; + int i; + uint8_t *p = (uint8_t *)audio_codec_wov_lang_buf_addr; + + SHA256_init(&ctx); + SHA256_update(&ctx, data, len); + digest = SHA256_final(&ctx); + +#ifdef DEBUG_AUDIO_CODEC + CPRINTS("data=%08x len=%d", data, len); + hexdump(digest, SHA256_DIGEST_SIZE); +#endif + + if (memcmp(digest, hash, SHA256_DIGEST_SIZE) != 0) + return EC_ERROR_UNKNOWN; + + for (i = len; i < LANG_BUF_LEN; ++i) + if (p[i]) + return EC_ERROR_UNKNOWN; + + return EC_SUCCESS; +} + +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_LANG_SHM +static int wov_set_lang_shm(struct host_cmd_handler_args *args) +{ + const struct ec_param_ec_codec_wov *p = args->params; + const struct ec_param_ec_codec_wov_set_lang_shm *pp = + &p->set_lang_shm_param; + + if (pp->total_len > LANG_BUF_LEN) + return EC_RES_INVALID_PARAM; + if (wov_enabled) + return EC_RES_BUSY; + + if (check_lang_buf((uint8_t *)audio_codec_wov_lang_buf_addr, + pp->total_len, pp->hash) != EC_SUCCESS) + return EC_RES_ERROR; + + memcpy(lang_hash, pp->hash, sizeof(lang_hash)); + lang_len = pp->total_len; + + args->response_size = 0; + return EC_RES_SUCCESS; +} +#else +static int wov_set_lang(struct host_cmd_handler_args *args) +{ + const struct ec_param_ec_codec_wov *p = args->params; + const struct ec_param_ec_codec_wov_set_lang *pp = &p->set_lang_param; + + if (pp->total_len > LANG_BUF_LEN) + return EC_RES_INVALID_PARAM; + if (pp->offset >= LANG_BUF_LEN) + return EC_RES_INVALID_PARAM; + if (pp->len > ARRAY_SIZE(pp->buf)) + return EC_RES_INVALID_PARAM; + if (pp->offset + pp->len > pp->total_len) + return EC_RES_INVALID_PARAM; + if (wov_enabled) + return EC_RES_BUSY; + + if (!pp->offset) + memset((uint8_t *)audio_codec_wov_lang_buf_addr, + 0, LANG_BUF_LEN); + + memcpy((uint8_t *)audio_codec_wov_lang_buf_addr + pp->offset, + pp->buf, pp->len); + + if (pp->offset + pp->len == pp->total_len) { + if (check_lang_buf((uint8_t *)audio_codec_wov_lang_buf_addr, + pp->total_len, pp->hash) != EC_SUCCESS) + return EC_RES_ERROR; + + memcpy(lang_hash, pp->hash, sizeof(lang_hash)); + lang_len = pp->total_len; + } + + args->response_size = 0; + return EC_RES_SUCCESS; +} +#endif /* CONFIG_AUDIO_CODEC_CAP_WOV_LANG_SHM */ + +static int wov_get_lang(struct host_cmd_handler_args *args) +{ + struct ec_response_ec_codec_wov_get_lang *r = args->response; + + memcpy(r->hash, lang_hash, sizeof(r->hash)); + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} + +static int wov_enable(struct host_cmd_handler_args *args) +{ + if (wov_enabled) + return EC_RES_BUSY; + + if (audio_codec_wov_enable() != EC_SUCCESS) + return EC_RES_ERROR; + + mutex_lock(&lock); + wov_enabled = 1; + hotword_detected = 0; + audio_buf_rp = audio_buf_wp = 0; + mutex_unlock(&lock); + +#ifdef HAS_TASK_WOV + task_wake(TASK_ID_WOV); +#endif + + args->response_size = 0; + return EC_RES_SUCCESS; +} + +static int wov_disable(struct host_cmd_handler_args *args) +{ + if (!wov_enabled) + return EC_RES_BUSY; + + if (audio_codec_wov_disable() != EC_SUCCESS) + return EC_RES_ERROR; + + mutex_lock(&lock); + wov_enabled = 0; + hotword_detected = 0; + audio_buf_rp = audio_buf_wp = 0; + mutex_unlock(&lock); + + args->response_size = 0; + return EC_RES_SUCCESS; +} + +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_AUDIO_SHM +static int wov_read_audio_shm(struct host_cmd_handler_args *args) +{ + struct ec_response_ec_codec_wov_read_audio_shm *r = args->response; + + if (!wov_enabled) + return EC_RES_ACCESS_DENIED; + + mutex_lock(&lock); + if (!hotword_detected) { + mutex_unlock(&lock); + return EC_RES_ACCESS_DENIED; + } + + r->offset = audio_buf_rp; + if (audio_buf_rp <= audio_buf_wp) + r->len = audio_buf_wp - audio_buf_rp; + else + r->len = AUDIO_BUF_LEN - audio_buf_rp; + + audio_buf_rp += r->len; + if (audio_buf_rp == AUDIO_BUF_LEN) + audio_buf_rp = 0; + mutex_unlock(&lock); + +#ifdef DEBUG_AUDIO_CODEC + if (!r->len) + CPRINTS("underrun detected"); +#endif + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} +#else +static int wov_read_audio(struct host_cmd_handler_args *args) +{ + struct ec_response_ec_codec_wov_read_audio *r = args->response; + uint8_t *p; + + if (!wov_enabled) + return EC_RES_ACCESS_DENIED; + + mutex_lock(&lock); + if (!hotword_detected) { + mutex_unlock(&lock); + return EC_RES_ACCESS_DENIED; + } + + if (audio_buf_rp <= audio_buf_wp) + r->len = audio_buf_wp - audio_buf_rp; + else + r->len = AUDIO_BUF_LEN - audio_buf_rp; + r->len = MIN(sizeof(r->buf), r->len); + + p = (uint8_t *)audio_codec_wov_audio_buf_addr + audio_buf_rp; + + audio_buf_rp += r->len; + if (audio_buf_rp == AUDIO_BUF_LEN) + audio_buf_rp = 0; + mutex_unlock(&lock); + +#ifdef DEBUG_AUDIO_CODEC + if (!r->len) + CPRINTS("underrun detected"); +#endif + /* + * Note: it is possible to copy corrupted audio data if overrun + * happened at the point. To keep it simple and align to SHM mode, + * we ignore the case if overrun happened. + */ + memcpy(r->buf, p, r->len); + + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} +#endif /* CONFIG_AUDIO_CODEC_CAP_WOV_AUDIO_SHM */ + +static int (*sub_cmds[])(struct host_cmd_handler_args *) = { +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_LANG_SHM + [EC_CODEC_WOV_SET_LANG_SHM] = wov_set_lang_shm, +#else + [EC_CODEC_WOV_SET_LANG] = wov_set_lang, +#endif + [EC_CODEC_WOV_GET_LANG] = wov_get_lang, + [EC_CODEC_WOV_ENABLE] = wov_enable, + [EC_CODEC_WOV_DISABLE] = wov_disable, +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_AUDIO_SHM + [EC_CODEC_WOV_READ_AUDIO_SHM] = wov_read_audio_shm, +#else + [EC_CODEC_WOV_READ_AUDIO] = wov_read_audio, +#endif +}; + +#ifdef DEBUG_AUDIO_CODEC +static char *strcmd[] = { +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_LANG_SHM + [EC_CODEC_WOV_SET_LANG_SHM] = "EC_CODEC_WOV_SET_LANG_SHM", +#else + [EC_CODEC_WOV_SET_LANG] = "EC_CODEC_WOV_SET_LANG", +#endif + [EC_CODEC_WOV_GET_LANG] = "EC_CODEC_WOV_GET_LANG", + [EC_CODEC_WOV_ENABLE] = "EC_CODEC_WOV_ENABLE", + [EC_CODEC_WOV_DISABLE] = "EC_CODEC_WOV_DISABLE", +#ifdef CONFIG_AUDIO_CODEC_CAP_WOV_AUDIO_SHM + [EC_CODEC_WOV_READ_AUDIO_SHM] = "EC_CODEC_WOV_READ_AUDIO_SHM", +#else + [EC_CODEC_WOV_READ_AUDIO] = "EC_CODEC_WOV_READ_AUDIO", +#endif +}; +BUILD_ASSERT(ARRAY_SIZE(sub_cmds) == ARRAY_SIZE(strcmd)); +#endif + +static int wov_host_command(struct host_cmd_handler_args *args) +{ + const struct ec_param_ec_codec_wov *p = args->params; + +#ifdef DEBUG_AUDIO_CODEC + CPRINTS("WoV subcommand: %s", strcmd[p->cmd]); +#endif + + if (p->cmd < EC_CODEC_WOV_SUBCMD_COUNT && sub_cmds[p->cmd]) + return sub_cmds[p->cmd](args); + + return EC_RES_INVALID_PARAM; +} +DECLARE_HOST_COMMAND(EC_CMD_EC_CODEC_WOV, wov_host_command, EC_VER_MASK(0)); + +/* + * Exported interfaces. + */ +void audio_codec_wov_task(void *arg) +{ + uint32_t n, req; + uint8_t *p; + + while (1) { + mutex_lock(&lock); + if (!wov_enabled) { + mutex_unlock(&lock); + task_wait_event(-1); + continue; + } + + + /* Clear the buffer if full. */ + if (is_buf_full()) { + audio_buf_wp = audio_buf_rp; + +#ifdef DEBUG_AUDIO_CODEC + if (hotword_detected) + CPRINTS("overrun detected"); +#endif + } + + /* + * Note: sample width is 16-bit. + * + * The linear ring buffer wastes one sample bytes to + * detect buffer full. + * + * If buffer is empty, maximum req is BUF_LEN - 2. + * If wp > rp, wp can fill to the end of linear buffer. + * If wp < rp, wp can fill up to rp - 2. + */ + if (audio_buf_wp == audio_buf_rp) + req = AUDIO_BUF_LEN - MAX(audio_buf_wp, 2); + else if (audio_buf_wp > audio_buf_rp) + req = AUDIO_BUF_LEN - audio_buf_wp; + else + req = audio_buf_rp - audio_buf_wp - 2; + + p = (uint8_t *)audio_codec_wov_audio_buf_addr + audio_buf_wp; + mutex_unlock(&lock); + + n = audio_codec_wov_read(p, req); + if (n < 0) { + CPRINTS("failed to read: %d", n); + break; + } else if (n == 0) { + if (audio_codec_wov_enable_notifier() != EC_SUCCESS) { + CPRINTS("failed to enable_notifier"); + break; + } + + task_wait_event(-1); + continue; + } + + mutex_lock(&lock); + audio_buf_wp += n; + if (audio_buf_wp == AUDIO_BUF_LEN) + audio_buf_wp = 0; + mutex_unlock(&lock); + + /* + * Reasons to sleep here: + * 1. read the audio data in a fixed pace (10ms) + * 2. yield the processor in case of watchdog thought EC crashed + */ + task_wait_event(10 * MSEC); + } +} diff --git a/common/build.mk b/common/build.mk index 19a55699d1..4475873eec 100644 --- a/common/build.mk +++ b/common/build.mk @@ -32,6 +32,7 @@ common-$(CONFIG_AP_HANG_DETECT)+=ap_hang_detect.o common-$(CONFIG_AUDIO_CODEC)+=audio_codec.o common-$(CONFIG_AUDIO_CODEC_DMIC)+=audio_codec_dmic.o common-$(CONFIG_AUDIO_CODEC_I2S_RX)+=audio_codec_i2s_rx.o +common-$(CONFIG_AUDIO_CODEC_WOV)+=audio_codec_wov.o common-$(CONFIG_BACKLIGHT_LID)+=backlight_lid.o common-$(CONFIG_BASE32)+=base32.o common-$(CONFIG_DETACHABLE_BASE)+=base_state.o diff --git a/include/audio_codec.h b/include/audio_codec.h index af64deb16d..62009e6c73 100644 --- a/include/audio_codec.h +++ b/include/audio_codec.h @@ -176,4 +176,79 @@ int audio_codec_i2s_rx_set_daifmt(uint8_t daifmt); */ int audio_codec_i2s_rx_set_bclk(uint32_t bclk); + +/* + * WoV abstract layer + */ + +/* + * Enables WoV. + * + * Returns: + * EC_SUCCESS if success. + * EC_ERROR_UNKNOWN if internal error. + * EC_ERROR_BUSY if has enabled. + */ +int audio_codec_wov_enable(void); + +/* + * Disables WoV. + * + * Returns: + * EC_SUCCESS if success. + * EC_ERROR_UNKNOWN if internal error. + * EC_ERROR_BUSY if has not enabled. + */ +int audio_codec_wov_disable(void); + +/* + * Reads the WoV audio data from chip. + * + * @buf is the target pointer to put the data. + * @count is the maximum number of bytes to read. + * + * Returns: + * -1 if any errors. + * 0 if no data. + * >0 if success. The returned value denotes number of bytes read. + */ +int32_t audio_codec_wov_read(void *buf, uint32_t count); + +/* + * Enables notification if WoV audio data is available. + * + * Returns: + * EC_SUCCESS if success. + * EC_ERROR_UNKNOWN if internal error. + * EC_ERROR_BUSY if has enabled. + * EC_ERROR_ACCESS_DENIED if the notifiee has not set. + */ +int audio_codec_wov_enable_notifier(void); + +/* + * Disables WoV data notification. + * + * Returns: + * EC_SUCCESS if success. + * EC_ERROR_UNKNOWN if internal error. + * EC_ERROR_BUSY if has not enabled. + * EC_ERROR_ACCESS_DENIED if the notifiee has not set. + */ +int audio_codec_wov_disable_notifier(void); + +/* + * Audio buffer for 2 seconds S16_LE, 16kHz, mono. + */ +extern uintptr_t audio_codec_wov_audio_buf_addr; + +/* + * Language model buffer for speech-micro. At least 67KB. + */ +extern uintptr_t audio_codec_wov_lang_buf_addr; + +/* + * Task for running WoV. + */ +void audio_codec_wov_task(void *arg); + #endif diff --git a/include/config.h b/include/config.h index 6483704444..bf74c03b4f 100644 --- a/include/config.h +++ b/include/config.h @@ -352,6 +352,9 @@ /* Support audio codec. */ #undef CONFIG_AUDIO_CODEC +/* Audio codec caps. */ +#undef CONFIG_AUDIO_CODEC_CAP_WOV_AUDIO_SHM +#undef CONFIG_AUDIO_CODEC_CAP_WOV_LANG_SHM /* Support audio codec on DMIC. */ #undef CONFIG_AUDIO_CODEC_DMIC /* Support audio codec software gain on DMIC. */ @@ -359,6 +362,13 @@ #undef CONFIG_AUDIO_CODEC_DMIC_MAX_SOFTWARE_GAIN /* Support audio codec on I2S RX. */ #undef CONFIG_AUDIO_CODEC_I2S_RX +/* Support audio codec on WoV. */ +#undef CONFIG_AUDIO_CODEC_WOV +/* Audio codec buffers. */ +#undef CONFIG_AUDIO_CODEC_WOV_AUDIO_BUF_LEN +#undef CONFIG_AUDIO_CODEC_WOV_AUDIO_BUF_TYPE +#undef CONFIG_AUDIO_CODEC_WOV_LANG_BUF_LEN +#undef CONFIG_AUDIO_CODEC_WOV_LANG_BUF_TYPE /* Allow proprietary communication protocols' extensions. */ #undef CONFIG_EXTENSION_COMMAND @@ -4942,4 +4952,9 @@ #endif /* CONFIG_USB_PD_DISCHARGE_GPIO */ #endif /* CONFIG_USB_PD_DISCHARGE */ +/* EC Codec Wake-on-Voice related definitions */ +#ifdef CONFIG_AUDIO_CODEC_WOV +#define CONFIG_SHA256 +#endif + #endif /* __CROS_EC_CONFIG_H */ diff --git a/include/ec_commands.h b/include/ec_commands.h index e553ecdfa9..30783bc1bb 100644 --- a/include/ec_commands.h +++ b/include/ec_commands.h @@ -641,6 +641,9 @@ enum host_event_code { /* Keyboard recovery combo with hardware reinitialization */ EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT = 30, + /* WoV */ + EC_HOST_EVENT_WOV = 31, + /* * The high bit of the event mask is not used as a host event code. If * it reads back as set, then the entire event mask should be @@ -4670,10 +4673,14 @@ enum ec_codec_subcmd { }; enum ec_codec_cap { + EC_CODEC_CAP_WOV_AUDIO_SHM = 0, + EC_CODEC_CAP_WOV_LANG_SHM = 1, EC_CODEC_CAP_LAST = 32, }; enum ec_codec_shm_id { + EC_CODEC_SHM_ID_WOV_AUDIO = 0x0, + EC_CODEC_SHM_ID_WOV_LANG = 0x1, EC_CODEC_SHM_ID_LAST, }; @@ -4835,6 +4842,68 @@ struct __ec_align4 ec_param_ec_codec_i2s_rx { }; /*****************************************************************************/ +/* Commands for WoV on audio codec. */ + +#define EC_CMD_EC_CODEC_WOV 0x00BF + +enum ec_codec_wov_subcmd { + EC_CODEC_WOV_SET_LANG = 0x0, + EC_CODEC_WOV_SET_LANG_SHM = 0x1, + EC_CODEC_WOV_GET_LANG = 0x2, + EC_CODEC_WOV_ENABLE = 0x3, + EC_CODEC_WOV_DISABLE = 0x4, + EC_CODEC_WOV_READ_AUDIO = 0x5, + EC_CODEC_WOV_READ_AUDIO_SHM = 0x6, + EC_CODEC_WOV_SUBCMD_COUNT, +}; + +/* + * @hash is SHA256 of the whole language model. + * @total_len indicates the length of whole language model. + * @offset is the cursor from the beginning of the model. + * @buf is the packet buffer. + * @len denotes how many bytes in the buf. + */ +struct __ec_align4 ec_param_ec_codec_wov_set_lang { + uint8_t hash[32]; + uint32_t total_len; + uint32_t offset; + uint8_t buf[128]; + uint32_t len; +}; + +struct __ec_align4 ec_param_ec_codec_wov_set_lang_shm { + uint8_t hash[32]; + uint32_t total_len; +}; + +struct __ec_align4 ec_param_ec_codec_wov { + uint8_t cmd; /* enum ec_codec_wov_subcmd */ + uint8_t reserved[3]; + + union { + struct ec_param_ec_codec_wov_set_lang + set_lang_param; + struct ec_param_ec_codec_wov_set_lang_shm + set_lang_shm_param; + }; +}; + +struct __ec_align4 ec_response_ec_codec_wov_get_lang { + uint8_t hash[32]; +}; + +struct __ec_align4 ec_response_ec_codec_wov_read_audio { + uint8_t buf[128]; + uint32_t len; +}; + +struct __ec_align4 ec_response_ec_codec_wov_read_audio_shm { + uint32_t offset; + uint32_t len; +}; + +/*****************************************************************************/ /* System commands */ /* |