blob: 629ae4e6464cf0b0f815e2b62d973079919d73c7 [file] [log] [blame]
/* sound/soc/samsung/abox/abox_msg.c
*
* ALSA SoC Audio Layer - 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 1000000
#define FLUSH_TIMEOUT_NS (TIMEOUT_NS * 20)
#define min(a, b) (a < b ? a : b)
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) {
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 = sizeof(q_cmd->elem) / sizeof(q_cmd->elem[0]);
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 = sizeof(q_cmd->elem) / sizeof(q_cmd->elem[0]);
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;
}