| /* |
| * Copyright (c) 2022 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/arch/xtensa/cache.h> |
| #include <zephyr/logging/log_backend.h> |
| #include <zephyr/logging/log_core.h> |
| #include <zephyr/logging/log_output.h> |
| #include <zephyr/logging/log_output_dict.h> |
| #include <zephyr/logging/log_backend_std.h> |
| #include <zephyr/logging/log_backend_adsp_hda.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/kernel.h> |
| |
| static uint32_t log_format_current = CONFIG_LOG_BACKEND_ADSP_HDA_OUTPUT_DEFAULT; |
| static const struct device *hda_log_dev; |
| static uint32_t hda_log_chan; |
| |
| /* |
| * HDA requires 128 byte aligned data and 128 byte aligned transfers. |
| */ |
| #define ALIGNMENT DMA_BUF_ALIGNMENT(DT_NODELABEL(hda_host_in)) |
| static __aligned(ALIGNMENT) uint8_t hda_log_buf[CONFIG_LOG_BACKEND_ADSP_HDA_SIZE]; |
| static volatile uint32_t hda_log_buffered; |
| static struct k_spinlock hda_log_lock; |
| static struct k_timer hda_log_timer; |
| static adsp_hda_log_hook_t hook; |
| |
| /* atomic bit flags for state */ |
| #define HDA_LOG_DMA_READY 0 |
| #define HDA_LOG_PANIC_MODE 1 |
| static atomic_t hda_log_flags; |
| |
| static uint32_t hda_log_flush(void) |
| { |
| uint32_t nearest128 = hda_log_buffered & ~((128) - 1); |
| |
| if (nearest128 > 0) { |
| hda_log_buffered = hda_log_buffered - nearest128; |
| #if !(IS_ENABLED(CONFIG_KERNEL_COHERENCE)) |
| z_xtensa_cache_flush(hda_log_buf, CONFIG_LOG_BACKEND_ADSP_HDA_SIZE); |
| #endif |
| dma_reload(hda_log_dev, hda_log_chan, 0, 0, nearest128); |
| } |
| |
| return nearest128; |
| } |
| |
| |
| static int hda_log_out(uint8_t *data, size_t length, void *ctx) |
| { |
| int ret; |
| bool do_log_flush; |
| struct dma_status status; |
| |
| k_spinlock_key_t key = k_spin_lock(&hda_log_lock); |
| |
| /* Defaults when DMA not yet initialized */ |
| uint32_t dma_free = sizeof(hda_log_buf); |
| uint32_t write_pos = 0; |
| |
| if (atomic_test_bit(&hda_log_flags, HDA_LOG_DMA_READY)) { |
| ret = dma_get_status(hda_log_dev, hda_log_chan, &status); |
| if (ret != 0) { |
| ret = length; |
| goto out; |
| } |
| |
| /* The hardware tells us what space we have available, and where to |
| * start writing. If the buffer is full we have no space. |
| */ |
| if (status.free <= 0) { |
| ret = length; |
| goto out; |
| } |
| |
| dma_free = status.free; |
| write_pos = status.write_position; |
| } |
| |
| /* Account for buffered writes since last dma_reload |
| * |
| * No underflow should be possible here, status.free is the apparent |
| * free space in the buffer from the DMA's read/write positions. |
| * When dma_reload is called status.free may be reduced by |
| * the nearest 128 divisible value of hda_log_buffered, |
| * where hda_log_buffered is then subtracted by the same amount. |
| * After which status.free should only increase in value. |
| * |
| * Assert this trueth though, just in case. |
| */ |
| __ASSERT_NO_MSG(dma_free > hda_log_buffered); |
| uint32_t available = dma_free - hda_log_buffered; |
| |
| /* If there isn't enough space for the message there's an overflow */ |
| if (available < length) { |
| ret = length; |
| goto out; |
| } |
| |
| /* Copy over the message to the buffer */ |
| uint32_t idx = write_pos + hda_log_buffered; |
| |
| if (idx > sizeof(hda_log_buf)) { |
| idx -= sizeof(hda_log_buf); |
| } |
| |
| size_t copy_len = (idx + length) < sizeof(hda_log_buf) ? length : sizeof(hda_log_buf) - idx; |
| |
| memcpy(&hda_log_buf[idx], data, copy_len); |
| |
| /* There may be a wrapped copy */ |
| size_t wrap_copy_len = length - copy_len; |
| |
| if (wrap_copy_len != 0) { |
| memcpy(&hda_log_buf[0], &data[copy_len], wrap_copy_len); |
| } |
| |
| ret = length; |
| hda_log_buffered += length; |
| |
| uint32_t written = 0; |
| |
| out: |
| /* If DMA_READY changes from unset to set during this call, that is |
| * perfectly acceptable. The default values for write_pos and dma_free |
| * are the correct values if that occurs. |
| */ |
| do_log_flush = ((hda_log_buffered > sizeof(hda_log_buf)/2) || |
| atomic_test_bit(&hda_log_flags, HDA_LOG_PANIC_MODE)) |
| && atomic_test_bit(&hda_log_flags, HDA_LOG_DMA_READY); |
| |
| if (do_log_flush) { |
| written = hda_log_flush(); |
| } |
| |
| k_spin_unlock(&hda_log_lock, key); |
| |
| /* The hook may have log calls and needs to be done outside of the spin |
| * lock to avoid recursion on the spin lock (deadlocks) in cases of |
| * direct logging. |
| */ |
| if (hook != NULL && written > 0) { |
| hook(written); |
| } |
| |
| return ret; |
| } |
| /** |
| * 128 bytes is the smallest transferrable size for HDA so use that |
| * and encompass almost all log lines in the formatter before flushing |
| * and memcpy'ing to the HDA buffer. |
| */ |
| #define LOG_BUF_SIZE 128 |
| static uint8_t log_buf[LOG_BUF_SIZE]; |
| LOG_OUTPUT_DEFINE(log_output_adsp_hda, hda_log_out, log_buf, LOG_BUF_SIZE); |
| |
| static void hda_log_periodic(struct k_timer *tm) |
| { |
| ARG_UNUSED(tm); |
| |
| k_spinlock_key_t key = k_spin_lock(&hda_log_lock); |
| |
| uint32_t written = hda_log_flush(); |
| |
| k_spin_unlock(&hda_log_lock, key); |
| |
| /* The hook may have log calls and needs to be done outside of the spin |
| * lock to avoid recursion on the spin lock (deadlocks) in cases of |
| * direct logging. |
| */ |
| if (hook != NULL && written > 0) { |
| hook(written); |
| } |
| } |
| |
| static inline void dropped(const struct log_backend *const backend, |
| uint32_t cnt) |
| { |
| ARG_UNUSED(backend); |
| |
| if (IS_ENABLED(CONFIG_LOG_DICTIONARY_SUPPORT)) { |
| log_dict_output_dropped_process(&log_output_adsp_hda, cnt); |
| } else { |
| log_output_dropped_process(&log_output_adsp_hda, cnt); |
| } |
| } |
| |
| static void panic(struct log_backend const *const backend) |
| { |
| ARG_UNUSED(backend); |
| |
| /* will immediately flush all future writes once set */ |
| atomic_set_bit(&hda_log_flags, HDA_LOG_PANIC_MODE); |
| |
| /* flushes the log queue */ |
| log_backend_std_panic(&log_output_adsp_hda); |
| } |
| |
| static int format_set(const struct log_backend *const backend, uint32_t log_type) |
| { |
| ARG_UNUSED(backend); |
| |
| log_format_current = log_type; |
| |
| return 0; |
| } |
| |
| static volatile uint32_t counter; |
| |
| static void process(const struct log_backend *const backend, |
| union log_msg_generic *msg) |
| { |
| ARG_UNUSED(backend); |
| uint32_t flags = log_backend_std_get_flags(); |
| |
| log_format_func_t log_output_func = log_format_func_t_get(log_format_current); |
| |
| log_output_func(&log_output_adsp_hda, &msg->log, flags); |
| } |
| |
| /** |
| * Lazily initialized, while the DMA may not be setup we continue |
| * to buffer log messages untilt he buffer is full. |
| */ |
| static void init(const struct log_backend *const backend) |
| { |
| ARG_UNUSED(backend); |
| |
| hda_log_buffered = 0; |
| } |
| |
| const struct log_backend_api log_backend_adsp_hda_api = { |
| .process = process, |
| .dropped = IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE) ? NULL : dropped, |
| .panic = panic, |
| .format_set = format_set, |
| .init = init, |
| }; |
| |
| LOG_BACKEND_DEFINE(log_backend_adsp_hda, log_backend_adsp_hda_api, true); |
| |
| void adsp_hda_log_init(adsp_hda_log_hook_t fn, uint32_t channel) |
| { |
| hook = fn; |
| |
| int res; |
| |
| hda_log_dev = DEVICE_DT_GET(DT_NODELABEL(hda_host_in)); |
| __ASSERT(device_is_ready(hda_log_dev), "DMA device is not ready"); |
| |
| hda_log_chan = dma_request_channel(hda_log_dev, &channel); |
| __ASSERT(hda_log_chan >= 0, "No valid DMA channel"); |
| __ASSERT(hda_log_chan == channel, "Not requested channel"); |
| |
| hda_log_buffered = 0; |
| |
| /* configure channel */ |
| struct dma_block_config hda_log_dma_blk_cfg = { |
| .block_size = CONFIG_LOG_BACKEND_ADSP_HDA_SIZE, |
| .source_address = (uint32_t)(uintptr_t)&hda_log_buf, |
| }; |
| |
| struct dma_config hda_log_dma_cfg = { |
| .channel_direction = MEMORY_TO_HOST, |
| .block_count = 1, |
| .head_block = &hda_log_dma_blk_cfg, |
| .source_data_size = 4, |
| }; |
| |
| res = dma_config(hda_log_dev, hda_log_chan, &hda_log_dma_cfg); |
| __ASSERT(res == 0, "DMA config failed"); |
| |
| res = dma_start(hda_log_dev, hda_log_chan); |
| __ASSERT(res == 0, "DMA start failed"); |
| |
| atomic_set_bit(&hda_log_flags, HDA_LOG_DMA_READY); |
| |
| k_timer_init(&hda_log_timer, hda_log_periodic, NULL); |
| k_timer_start(&hda_log_timer, |
| K_MSEC(CONFIG_LOG_BACKEND_ADSP_HDA_FLUSH_TIME), |
| K_MSEC(CONFIG_LOG_BACKEND_ADSP_HDA_FLUSH_TIME)); |
| |
| printk("hda log initialized\n"); |
| } |