// SPDX-License-Identifier: GPL-2.0-or-later /* * ALSA SoC - Samsung Abox Message Queue driver * * Copyright (c) 2017 Samsung Electronics Co. Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "abox_msg.h" #define TIMEOUT_NS 10000000 #define FLUSH_TIMEOUT_NS (TIMEOUT_NS * 20) static struct abox_msg_queue *tx; static struct abox_msg_queue *rx; static void (*tx_lock)(void); static void (*tx_unlock)(void); static void (*rx_lock)(void); static void (*rx_unlock)(void); static uint64_t (*get_time)(void); static void (*print_log)(const char *fmt, ...); static int32_t ret_ok; static int32_t ret_err; static int is_avail(struct abox_msg_data_queue *q_data, int32_t size) { volatile int32_t *idx_s = &q_data->idx_s; int blank; if (*idx_s > q_data->idx_e) { blank = q_data->idx_s - q_data->idx_e; } else { blank = q_data->len - q_data->idx_e; if (blank < size) { /* reset end index only if there is a space */ if (q_data->idx_s >= size) { q_data->idx_e = 0; blank = q_data->idx_s - q_data->idx_e; } } } return blank >= size; } int32_t abox_msg_send(struct abox_msg_cmd *cmd, const struct abox_msg_send_data *data, int count) { struct abox_msg_data *p_data; struct abox_msg_cmd_queue *q_cmd = &tx->q_cmd; struct abox_msg_data_queue *q_data = &tx->q_data; volatile int32_t *idx_s = &q_cmd->idx_s; uint32_t cmd_len, data_len, data_size; int32_t ret = ret_ok; int i; int busy_log; data_len = q_data->len; data_size = offsetof(struct abox_msg_data, data); for (i = 0; i < count; i++) data_size += data[i].size; cmd_len = ARRAY_SIZE(q_cmd->elem); cmd->time_put = get_time(); cmd->time_get = 0; tx_lock(); /* queue data */ busy_log = 0; while (!is_avail(q_data, data_size)) { if (!busy_log) { print_log("%s: busy\n", __func__); busy_log = 1; } if (get_time() - cmd->time_put > TIMEOUT_NS) { print_log("%s: timeout: s=%d, e=%d, size=%u\n", __func__, q_data->idx_s, q_data->idx_e, data_size); ret = ret_err; goto unlock; } } p_data = (struct abox_msg_data *)&q_data->elem[q_data->idx_e]; q_data->idx_e = (q_data->idx_e + data_size) % data_len; p_data->size = 0; for (i = 0; i < count; i++) { memcpy(p_data->data + p_data->size, data[i].data, data[i].size); p_data->size += data[i].size; } /* queue cmd */ cmd->data_idx = (int8_t *)p_data - q_data->elem; cmd->send.data = p_data; busy_log = 0; while (*idx_s == ((q_cmd->idx_e + 1) % cmd_len)) { if (!busy_log) { print_log("%s: busy\n", __func__); busy_log = 1; } if (get_time() - cmd->time_put > TIMEOUT_NS) { print_log("%s: timeout: %d, %d, %d, %d, %d, %llu\n", __func__, cmd->id, cmd->cmd, cmd->arg[0], cmd->arg[1], cmd->arg[2], cmd->time_put); ret = ret_err; goto unlock; } } q_cmd->elem[q_cmd->idx_e] = *cmd; /* complete memcpy before increasing queue index */ mb(); q_cmd->idx_e = (q_cmd->idx_e + 1) % cmd_len; unlock: tx_unlock(); return ret; } int32_t abox_msg_flush(void) { struct abox_msg_cmd_queue *q_cmd = &tx->q_cmd; volatile int32_t *idx_s = &q_cmd->idx_s; uint64_t time = get_time(); while (*idx_s != q_cmd->idx_e) { if (get_time() - time > FLUSH_TIMEOUT_NS) { struct abox_msg_cmd *cmd; cmd = &q_cmd->elem[*idx_s]; print_log("%s: timeout: %d, %d, %d, %d, %d, %llu\n", __func__, cmd->id, cmd->cmd, cmd->arg[0], cmd->arg[1], cmd->arg[2], cmd->time_put); return ret_err; } } return ret_ok; } int32_t abox_msg_recv(struct abox_msg_cmd *cmd, void *data, int32_t size) { struct abox_msg_cmd_queue *q_cmd = &rx->q_cmd; struct abox_msg_data_queue *q_data = &rx->q_data; volatile int32_t *idx_e = &q_cmd->idx_e; struct abox_msg_cmd *p_cmd; struct abox_msg_data *p_data; uint32_t cmd_len, data_len, data_size; int32_t ret; cmd_len = ARRAY_SIZE(q_cmd->elem); data_len = q_data->len; rx_lock(); if (q_cmd->idx_s == *idx_e) { /* ipc read msg queue until empty on every interrupt. * Reporting it through log is nothing but annoying. * print_log("%s: empty\n", __func__); */ ret = ret_err; goto unlock; } p_cmd = &q_cmd->elem[q_cmd->idx_s]; p_data = (struct abox_msg_data *)&q_data->elem[p_cmd->data_idx]; if (size < p_data->size) { print_log("%s: buffer size(%u) < received size(%u)\n", __func__, size, p_data->size); } p_cmd->time_get = get_time(); p_cmd->recv.data = p_data; if (cmd) *cmd = *p_cmd; if (data) memcpy(data, p_data->data, min(size, p_data->size)); ret = p_data->size; data_size = p_data->size + offsetof(struct abox_msg_data, data); q_cmd->idx_s = (q_cmd->idx_s + 1) % cmd_len; q_data->idx_s = (p_cmd->data_idx + data_size) % data_len; unlock: rx_unlock(); return ret; } int32_t abox_msg_init(const struct abox_msg_cfg *cfg) { unsigned int data_offset = offsetof(struct abox_msg_queue, q_data); unsigned int elem_offset = offsetof(struct abox_msg_data_queue, elem); ret_ok = cfg->ret_ok; ret_err = cfg->ret_err; if (cfg->tx_size < sizeof(*tx) || cfg->rx_size < sizeof(*rx)) return ret_err; tx = cfg->tx_addr; tx->q_data.len = cfg->tx_size - data_offset - elem_offset; rx = cfg->rx_addr; rx->q_data.len = cfg->rx_size - data_offset - elem_offset; tx_lock = cfg->tx_lock_f; tx_unlock = cfg->tx_unlock_f; rx_lock = cfg->rx_lock_f; rx_unlock = cfg->rx_unlock_f; get_time = cfg->get_time_f; print_log = cfg->print_log_f; return ret_ok; }