/* arch/arm/mach-msm/qdsp6/dal.c * * Copyright (C) 2009 Google, Inc. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dal.h" #define DAL_TRACE 0 struct dal_hdr { uint32_t length:16; /* message length (header inclusive) */ uint32_t version:8; /* DAL protocol version */ uint32_t priority:7; uint32_t async:1; uint32_t ddi:16; /* DDI method number */ uint32_t prototype:8; /* DDI serialization format */ uint32_t msgid:8; /* message id (DDI, ATTACH, DETACH, ...) */ void *from; void *to; } __attribute__((packed)); #define TRACE_DATA_MAX 128 #define TRACE_LOG_MAX 32 #define TRACE_LOG_MASK (TRACE_LOG_MAX - 1) struct dal_trace { unsigned timestamp; struct dal_hdr hdr; uint32_t data[TRACE_DATA_MAX]; }; #define DAL_HDR_SIZE (sizeof(struct dal_hdr)) #define DAL_DATA_MAX 512 #define DAL_MSG_MAX (DAL_HDR_SIZE + DAL_DATA_MAX) #define DAL_VERSION 0x11 #define DAL_MSGID_DDI 0x00 #define DAL_MSGID_ATTACH 0x01 #define DAL_MSGID_DETACH 0x02 #define DAL_MSGID_ASYNCH 0xC0 #define DAL_MSGID_REPLY 0x80 struct dal_channel { struct list_head list; struct list_head clients; /* synchronization for changing channel state, * adding/removing clients, smd callbacks, etc */ spinlock_t lock; struct smd_channel *sch; char *name; /* events are delivered at IRQ context immediately, so * we only need one assembly buffer for the entire channel */ struct dal_hdr hdr; unsigned char data[DAL_DATA_MAX]; unsigned count; void *ptr; /* client which the current inbound message is for */ struct dal_client *active; }; struct dal_client { struct list_head list; struct dal_channel *dch; void *cookie; dal_event_func_t event; /* opaque handle for the far side */ void *remote; /* dal rpc calls are fully synchronous -- only one call may be * active per client at a time */ struct mutex write_lock; wait_queue_head_t wait; unsigned char data[DAL_DATA_MAX]; void *reply; int reply_max; int status; unsigned msgid; /* msgid of expected reply */ spinlock_t tr_lock; unsigned tr_head; unsigned tr_tail; struct dal_trace *tr_log; }; static unsigned now(void) { struct timespec ts; ktime_get_ts(&ts); return (ts.tv_nsec / 1000000) + (ts.tv_sec * 1000); } void dal_trace(struct dal_client *c) { if (c->tr_log) return; c->tr_log = kzalloc(sizeof(struct dal_trace) * TRACE_LOG_MAX, GFP_KERNEL); } void dal_trace_print(struct dal_hdr *hdr, unsigned *data, int len, unsigned when) { int i; printk("DAL %08x -> %08x L=%03x A=%d D=%04x P=%02x M=%02x T=%d", (unsigned) hdr->from, (unsigned) hdr->to, hdr->length, hdr->async, hdr->ddi, hdr->prototype, hdr->msgid, when); len /= 4; for (i = 0; i < len; i++) { if (!(i & 7)) printk("\n%03x", i * 4); printk(" %08x", data[i]); } printk("\n"); } void dal_trace_dump(struct dal_client *c) { struct dal_trace *dt; unsigned n, len; if (!c->tr_log) return; for (n = c->tr_tail; n != c->tr_head; n = (n + 1) & TRACE_LOG_MASK) { dt = c->tr_log + n; len = dt->hdr.length - sizeof(dt->hdr); if (len > TRACE_DATA_MAX) len = TRACE_DATA_MAX; dal_trace_print(&dt->hdr, dt->data, len, dt->timestamp); } } static void dal_trace_log(struct dal_client *c, struct dal_hdr *hdr, void *data, unsigned len) { unsigned long flags; unsigned t, n; struct dal_trace *dt; t = now(); if (len > TRACE_DATA_MAX) len = TRACE_DATA_MAX; spin_lock_irqsave(&c->tr_lock, flags); n = (c->tr_head + 1) & TRACE_LOG_MASK; if (c->tr_tail == n) c->tr_tail = (c->tr_tail + 1) & TRACE_LOG_MASK; dt = c->tr_log + n; dt->timestamp = t; memcpy(&dt->hdr, hdr, sizeof(struct dal_hdr)); memcpy(dt->data, data, len); c->tr_head = n; spin_unlock_irqrestore(&c->tr_lock, flags); } static void dal_channel_notify(void *priv, unsigned event) { struct dal_channel *dch = priv; struct dal_hdr *hdr = &dch->hdr; struct dal_client *client; unsigned long flags; int len; int r; spin_lock_irqsave(&dch->lock, flags); again: if (dch->count == 0) { if (smd_read_avail(dch->sch) < DAL_HDR_SIZE) goto done; smd_read(dch->sch, hdr, DAL_HDR_SIZE); if (hdr->length < DAL_HDR_SIZE) goto done; if (hdr->length > DAL_MSG_MAX) panic("oversize message"); dch->count = hdr->length - DAL_HDR_SIZE; /* locate the client this message is targeted to */ list_for_each_entry(client, &dch->clients, list) { if (dch->hdr.to == client) { dch->active = client; dch->ptr = client->data; goto check_data; } } pr_err("[%s:%s] $$$ receiving unknown message len = %d $$$\n", __MM_FILE__, __func__, dch->count); dch->active = 0; dch->ptr = dch->data; } check_data: len = dch->count; if (len > 0) { if (smd_read_avail(dch->sch) < len) goto done; r = smd_read(dch->sch, dch->ptr, len); if (r != len) panic("invalid read"); #if DAL_TRACE pr_info("[%s:%s] dal recv %p <- %p %02x:%04x:%02x %d\n", __MM_FILE__, __func__, hdr->to, hdr->from, hdr->msgid, hdr->ddi, hdr->prototype, hdr->length - sizeof(*hdr)); print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, dch->ptr, len); #endif dch->count = 0; client = dch->active; if (!client) { pr_err("[%s:%s] message to %p discarded\n", __MM_FILE__, __func__, dch->hdr.to); goto again; } if (client->tr_log) dal_trace_log(client, hdr, dch->ptr, len); if (hdr->msgid == DAL_MSGID_ASYNCH) { if (client->event) client->event(dch->ptr, len, client->cookie); else pr_err("[%s:%s] client %p has no event \ handler\n", __MM_FILE__, __func__, client); goto again; } if (hdr->msgid == client->msgid) { if (!client->remote) client->remote = hdr->from; if (len > client->reply_max) len = client->reply_max; memcpy(client->reply, client->data, len); client->status = len; wake_up(&client->wait); goto again; } pr_err("[%s:%s] cannot find client %p\n", __MM_FILE__, __func__, dch->hdr.to); goto again; } done: spin_unlock_irqrestore(&dch->lock, flags); } static LIST_HEAD(dal_channel_list); static DEFINE_MUTEX(dal_channel_list_lock); static struct dal_channel *dal_open_channel(const char *name, uint32_t cpu) { struct dal_channel *dch; pr_debug("[%s:%s]\n", __MM_FILE__, __func__); mutex_lock(&dal_channel_list_lock); list_for_each_entry(dch, &dal_channel_list, list) { if (!strcmp(dch->name, name)) goto found_it; } dch = kzalloc(sizeof(*dch) + strlen(name) + 1, GFP_KERNEL); if (!dch) goto fail; dch->name = (char *) (dch + 1); strcpy(dch->name, name); spin_lock_init(&dch->lock); INIT_LIST_HEAD(&dch->clients); list_add(&dch->list, &dal_channel_list); found_it: if (!dch->sch) { if (smd_named_open_on_edge(name, cpu, &dch->sch, dch, dal_channel_notify)) { pr_err("[%s:%s] smd open failed\n", __MM_FILE__, __func__); dch = NULL; } /* FIXME: wait for channel to open before returning */ msleep(100); } fail: mutex_unlock(&dal_channel_list_lock); return dch; } int dal_call_raw(struct dal_client *client, struct dal_hdr *hdr, void *data, int data_len, void *reply, int reply_max) { struct dal_channel *dch = client->dch; unsigned long flags; client->reply = reply; client->reply_max = reply_max; client->msgid = hdr->msgid | DAL_MSGID_REPLY; client->status = -EBUSY; #if DAL_TRACE pr_info("[%s:%s:%x] dal send %p -> %p %02x:%04x:%02x %d\n", __MM_FILE__, __func__, (unsigned int)client, hdr->from, hdr->to, hdr->msgid, hdr->ddi, hdr->prototype, hdr->length - sizeof(*hdr)); print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, data, data_len); #endif if (client->tr_log) dal_trace_log(client, hdr, data, data_len); spin_lock_irqsave(&dch->lock, flags); /* FIXME: ensure entire message is written or none. */ smd_write(dch->sch, hdr, sizeof(*hdr)); smd_write(dch->sch, data, data_len); spin_unlock_irqrestore(&dch->lock, flags); if (!wait_event_timeout(client->wait, (client->status != -EBUSY), 5*HZ)) { dal_trace_dump(client); pr_err("[%s:%s] call timed out. dsp is probably dead.\n", __MM_FILE__, __func__); dal_trace_print(hdr, data, data_len, 0); q6audio_dsp_not_responding(); } return client->status; } int dal_call(struct dal_client *client, unsigned ddi, unsigned prototype, void *data, int data_len, void *reply, int reply_max) { struct dal_hdr hdr; int r; memset(&hdr, 0, sizeof(hdr)); hdr.length = data_len + sizeof(hdr); hdr.version = DAL_VERSION; hdr.msgid = DAL_MSGID_DDI; hdr.ddi = ddi; hdr.prototype = prototype; hdr.from = client; hdr.to = client->remote; if (hdr.length > DAL_MSG_MAX) return -EINVAL; mutex_lock(&client->write_lock); r = dal_call_raw(client, &hdr, data, data_len, reply, reply_max); mutex_unlock(&client->write_lock); return r; } struct dal_msg_attach { uint32_t device_id; char attach[64]; char service_name[32]; } __attribute__((packed)); struct dal_reply_attach { uint32_t status; char name[64]; }; struct dal_client *dal_attach(uint32_t device_id, const char *name, uint32_t cpu, dal_event_func_t func, void *cookie) { struct dal_hdr hdr; struct dal_msg_attach msg; struct dal_reply_attach reply; struct dal_channel *dch; struct dal_client *client; unsigned long flags; int r; pr_debug("[%s:%s]\n", __MM_FILE__, __func__); dch = dal_open_channel(name, cpu); if (!dch) return 0; client = kzalloc(sizeof(*client), GFP_KERNEL); if (!client) return 0; client->dch = dch; client->event = func; client->cookie = cookie; mutex_init(&client->write_lock); spin_lock_init(&client->tr_lock); init_waitqueue_head(&client->wait); spin_lock_irqsave(&dch->lock, flags); list_add(&client->list, &dch->clients); spin_unlock_irqrestore(&dch->lock, flags); memset(&hdr, 0, sizeof(hdr)); memset(&msg, 0, sizeof(msg)); hdr.length = sizeof(hdr) + sizeof(msg); hdr.version = DAL_VERSION; hdr.msgid = DAL_MSGID_ATTACH; hdr.from = client; msg.device_id = device_id; r = dal_call_raw(client, &hdr, &msg, sizeof(msg), &reply, sizeof(reply)); if ((r == sizeof(reply)) && (reply.status == 0)) { reply.name[63] = 0; pr_info("[%s:%s] status = %d, name = '%s' dal_client %x\n", __MM_FILE__, __func__, reply.status, reply.name, (unsigned int)client); return client; } pr_err("[%s:%s] failure\n", __MM_FILE__, __func__); dal_detach(client); return 0; } int dal_detach(struct dal_client *client) { struct dal_channel *dch; unsigned long flags; pr_debug("[%s:%s]\n", __MM_FILE__, __func__); mutex_lock(&client->write_lock); if (client->remote) { struct dal_hdr hdr; uint32_t data; memset(&hdr, 0, sizeof(hdr)); hdr.length = sizeof(hdr) + sizeof(data); hdr.version = DAL_VERSION; hdr.msgid = DAL_MSGID_DETACH; hdr.from = client; hdr.to = client->remote; data = (uint32_t) client; dal_call_raw(client, &hdr, &data, sizeof(data), &data, sizeof(data)); } dch = client->dch; spin_lock_irqsave(&dch->lock, flags); if (dch->active == client) { /* We have received a message header for this client * but not the body of the message. Ensure that when * the body arrives we don't write it into the now-closed * client. In *theory* this should never happen. */ dch->active = 0; dch->ptr = dch->data; } list_del(&client->list); spin_unlock_irqrestore(&dch->lock, flags); mutex_unlock(&client->write_lock); kfree(client); return 0; } void *dal_get_remote_handle(struct dal_client *client) { return client->remote; } /* convenience wrappers */ int dal_call_f0(struct dal_client *client, uint32_t ddi, uint32_t arg1) { uint32_t tmp = arg1; int res; res = dal_call(client, ddi, 0, &tmp, sizeof(tmp), &tmp, sizeof(tmp)); if (res >= 4) return (int) tmp; return res; } int dal_call_f1(struct dal_client *client, uint32_t ddi, uint32_t arg1, uint32_t arg2) { uint32_t tmp[2]; int res; tmp[0] = arg1; tmp[1] = arg2; res = dal_call(client, ddi, 1, tmp, sizeof(tmp), tmp, sizeof(uint32_t)); if (res >= 4) return (int) tmp[0]; return res; } int dal_call_f5(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen) { uint32_t tmp[128]; int res; int param_idx = 0; if (ilen + 4 > DAL_DATA_MAX) return -EINVAL; tmp[param_idx] = ilen; param_idx++; memcpy(&tmp[param_idx], ibuf, ilen); param_idx += DIV_ROUND_UP(ilen, 4); res = dal_call(client, ddi, 5, tmp, param_idx * 4, tmp, sizeof(tmp)); if (res >= 4) return (int) tmp[0]; return res; } int dal_call_f6(struct dal_client *client, uint32_t ddi, uint32_t s1, void *ibuf, uint32_t ilen) { uint32_t tmp[128]; int res; int param_idx = 0; if (ilen + 8 > DAL_DATA_MAX) return -EINVAL; tmp[param_idx] = s1; param_idx++; tmp[param_idx] = ilen; param_idx++; memcpy(&tmp[param_idx], ibuf, ilen); param_idx += DIV_ROUND_UP(ilen, 4); res = dal_call(client, ddi, 6, tmp, param_idx * 4, tmp, sizeof(tmp)); if (res >= 4) return (int) tmp[0]; return res; } int dal_call_f9(struct dal_client *client, uint32_t ddi, void *obuf, uint32_t olen) { uint32_t tmp[128]; int res; if (olen > sizeof(tmp) - 8) return -EINVAL; tmp[0] = olen; res = dal_call(client, ddi, 9, tmp, sizeof(uint32_t), tmp, sizeof(tmp)); if (res >= 4) res = (int)tmp[0]; if (!res) { if (tmp[1] > olen) return -EIO; memcpy(obuf, &tmp[2], tmp[1]); } return res; } int dal_call_f11(struct dal_client *client, uint32_t ddi, uint32_t s1, void *obuf, uint32_t olen) { uint32_t tmp[DAL_DATA_MAX/4] = {0}; int res; int param_idx = 0; int num_bytes = 4; num_bytes += (DIV_ROUND_UP(olen, 4)) * 4; if ((num_bytes > DAL_DATA_MAX - 12) || (olen > DAL_DATA_MAX - 8)) return -EINVAL; tmp[param_idx] = s1; param_idx++; tmp[param_idx] = olen; param_idx += DIV_ROUND_UP(olen, 4); res = dal_call(client, ddi, 11, tmp, param_idx * 4, tmp, sizeof(tmp)); if (res >= 4) res = (int) tmp[0]; if (!res) { if (tmp[1] > olen) return -EIO; memcpy(obuf, &tmp[2], tmp[1]); } return res; } int dal_call_f13(struct dal_client *client, uint32_t ddi, void *ibuf1, uint32_t ilen1, void *ibuf2, uint32_t ilen2, void *obuf, uint32_t olen) { uint32_t tmp[DAL_DATA_MAX/4]; int res; int param_idx = 0; int num_bytes = 0; num_bytes = (DIV_ROUND_UP(ilen1, 4)) * 4; num_bytes += (DIV_ROUND_UP(ilen2, 4)) * 4; if ((num_bytes > DAL_DATA_MAX - 12) || (olen > DAL_DATA_MAX - 8) || (ilen1 > DAL_DATA_MAX) || (ilen2 > DAL_DATA_MAX)) return -EINVAL; tmp[param_idx] = ilen1; param_idx++; memcpy(&tmp[param_idx], ibuf1, ilen1); param_idx += DIV_ROUND_UP(ilen1, 4); tmp[param_idx++] = ilen2; memcpy(&tmp[param_idx], ibuf2, ilen2); param_idx += DIV_ROUND_UP(ilen2, 4); tmp[param_idx++] = olen; res = dal_call(client, ddi, 13, tmp, param_idx * 4, tmp, sizeof(tmp)); if (res >= 4) res = (int)tmp[0]; if (!res) { if (tmp[1] > olen) return -EIO; memcpy(obuf, &tmp[2], tmp[1]); } return res; } int dal_call_f14(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen, void *obuf1, uint32_t olen1, void *obuf2, uint32_t olen2, uint32_t *oalen2) { uint32_t tmp[128]; int res; int param_idx = 0; if (olen1 + olen2 + 8 > DAL_DATA_MAX || ilen + 12 > DAL_DATA_MAX) return -EINVAL; tmp[param_idx] = ilen; param_idx++; memcpy(&tmp[param_idx], ibuf, ilen); param_idx += DIV_ROUND_UP(ilen, 4); tmp[param_idx++] = olen1; tmp[param_idx++] = olen2; res = dal_call(client, ddi, 14, tmp, param_idx * 4, tmp, sizeof(tmp)); if (res >= 4) res = (int)tmp[0]; if (!res) { if (tmp[1] > olen1) return -EIO; param_idx = DIV_ROUND_UP(tmp[1], 4) + 2; if (tmp[param_idx] > olen2) return -EIO; memcpy(obuf1, &tmp[2], tmp[1]); memcpy(obuf2, &tmp[param_idx+1], tmp[param_idx]); *oalen2 = tmp[param_idx]; } return res; }