![Cyril Roelandt](/assets/img/avatar_default.png)
delete_msg_mgr on a NULL pointer is a no-op, so the NULL check in bridge_msg_delete can be removed. Signed-off-by: Cyril Roelandt <tipecaml@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
565 lines
15 KiB
C
565 lines
15 KiB
C
/*
|
|
* msg_sm.c
|
|
*
|
|
* DSP-BIOS Bridge driver support functions for TI OMAP processors.
|
|
*
|
|
* Implements upper edge functions for Bridge message module.
|
|
*
|
|
* Copyright (C) 2005-2006 Texas Instruments, Inc.
|
|
*
|
|
* This package 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.
|
|
*
|
|
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
#include <linux/types.h>
|
|
|
|
/* ----------------------------------- DSP/BIOS Bridge */
|
|
#include <dspbridge/dbdefs.h>
|
|
|
|
/* ----------------------------------- OS Adaptation Layer */
|
|
#include <dspbridge/sync.h>
|
|
|
|
/* ----------------------------------- Platform Manager */
|
|
#include <dspbridge/dev.h>
|
|
|
|
/* ----------------------------------- Others */
|
|
#include <dspbridge/io_sm.h>
|
|
|
|
/* ----------------------------------- This */
|
|
#include <_msg_sm.h>
|
|
#include <dspbridge/dspmsg.h>
|
|
|
|
/* ----------------------------------- Function Prototypes */
|
|
static int add_new_msg(struct list_head *msg_list);
|
|
static void delete_msg_mgr(struct msg_mgr *hmsg_mgr);
|
|
static void delete_msg_queue(struct msg_queue *msg_queue_obj, u32 num_to_dsp);
|
|
static void free_msg_list(struct list_head *msg_list);
|
|
|
|
/*
|
|
* ======== bridge_msg_create ========
|
|
* Create an object to manage message queues. Only one of these objects
|
|
* can exist per device object.
|
|
*/
|
|
int bridge_msg_create(struct msg_mgr **msg_man,
|
|
struct dev_object *hdev_obj,
|
|
msg_onexit msg_callback)
|
|
{
|
|
struct msg_mgr *msg_mgr_obj;
|
|
struct io_mgr *hio_mgr;
|
|
int status = 0;
|
|
|
|
if (!msg_man || !msg_callback || !hdev_obj)
|
|
return -EFAULT;
|
|
|
|
dev_get_io_mgr(hdev_obj, &hio_mgr);
|
|
if (!hio_mgr)
|
|
return -EFAULT;
|
|
|
|
*msg_man = NULL;
|
|
/* Allocate msg_ctrl manager object */
|
|
msg_mgr_obj = kzalloc(sizeof(struct msg_mgr), GFP_KERNEL);
|
|
if (!msg_mgr_obj)
|
|
return -ENOMEM;
|
|
|
|
msg_mgr_obj->on_exit = msg_callback;
|
|
msg_mgr_obj->iomgr = hio_mgr;
|
|
/* List of MSG_QUEUEs */
|
|
INIT_LIST_HEAD(&msg_mgr_obj->queue_list);
|
|
/*
|
|
* Queues of message frames for messages to the DSP. Message
|
|
* frames will only be added to the free queue when a
|
|
* msg_queue object is created.
|
|
*/
|
|
INIT_LIST_HEAD(&msg_mgr_obj->msg_free_list);
|
|
INIT_LIST_HEAD(&msg_mgr_obj->msg_used_list);
|
|
spin_lock_init(&msg_mgr_obj->msg_mgr_lock);
|
|
|
|
/*
|
|
* Create an event to be used by bridge_msg_put() in waiting
|
|
* for an available free frame from the message manager.
|
|
*/
|
|
msg_mgr_obj->sync_event =
|
|
kzalloc(sizeof(struct sync_object), GFP_KERNEL);
|
|
if (!msg_mgr_obj->sync_event) {
|
|
kfree(msg_mgr_obj);
|
|
return -ENOMEM;
|
|
}
|
|
sync_init_event(msg_mgr_obj->sync_event);
|
|
|
|
*msg_man = msg_mgr_obj;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_create_queue ========
|
|
* Create a msg_queue for sending/receiving messages to/from a node
|
|
* on the DSP.
|
|
*/
|
|
int bridge_msg_create_queue(struct msg_mgr *hmsg_mgr, struct msg_queue **msgq,
|
|
u32 msgq_id, u32 max_msgs, void *arg)
|
|
{
|
|
u32 i;
|
|
u32 num_allocated = 0;
|
|
struct msg_queue *msg_q;
|
|
int status = 0;
|
|
|
|
if (!hmsg_mgr || msgq == NULL)
|
|
return -EFAULT;
|
|
|
|
*msgq = NULL;
|
|
/* Allocate msg_queue object */
|
|
msg_q = kzalloc(sizeof(struct msg_queue), GFP_KERNEL);
|
|
if (!msg_q)
|
|
return -ENOMEM;
|
|
|
|
msg_q->max_msgs = max_msgs;
|
|
msg_q->msg_mgr = hmsg_mgr;
|
|
msg_q->arg = arg; /* Node handle */
|
|
msg_q->msgq_id = msgq_id; /* Node env (not valid yet) */
|
|
/* Queues of Message frames for messages from the DSP */
|
|
INIT_LIST_HEAD(&msg_q->msg_free_list);
|
|
INIT_LIST_HEAD(&msg_q->msg_used_list);
|
|
|
|
/* Create event that will be signalled when a message from
|
|
* the DSP is available. */
|
|
msg_q->sync_event = kzalloc(sizeof(struct sync_object), GFP_KERNEL);
|
|
if (!msg_q->sync_event) {
|
|
status = -ENOMEM;
|
|
goto out_err;
|
|
|
|
}
|
|
sync_init_event(msg_q->sync_event);
|
|
|
|
/* Create a notification list for message ready notification. */
|
|
msg_q->ntfy_obj = kmalloc(sizeof(struct ntfy_object), GFP_KERNEL);
|
|
if (!msg_q->ntfy_obj) {
|
|
status = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
ntfy_init(msg_q->ntfy_obj);
|
|
|
|
/* Create events that will be used to synchronize cleanup
|
|
* when the object is deleted. sync_done will be set to
|
|
* unblock threads in MSG_Put() or MSG_Get(). sync_done_ack
|
|
* will be set by the unblocked thread to signal that it
|
|
* is unblocked and will no longer reference the object. */
|
|
msg_q->sync_done = kzalloc(sizeof(struct sync_object), GFP_KERNEL);
|
|
if (!msg_q->sync_done) {
|
|
status = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
sync_init_event(msg_q->sync_done);
|
|
|
|
msg_q->sync_done_ack = kzalloc(sizeof(struct sync_object), GFP_KERNEL);
|
|
if (!msg_q->sync_done_ack) {
|
|
status = -ENOMEM;
|
|
goto out_err;
|
|
}
|
|
sync_init_event(msg_q->sync_done_ack);
|
|
|
|
/* Enter critical section */
|
|
spin_lock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
/* Initialize message frames and put in appropriate queues */
|
|
for (i = 0; i < max_msgs && !status; i++) {
|
|
status = add_new_msg(&hmsg_mgr->msg_free_list);
|
|
if (!status) {
|
|
num_allocated++;
|
|
status = add_new_msg(&msg_q->msg_free_list);
|
|
}
|
|
}
|
|
if (status) {
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
goto out_err;
|
|
}
|
|
|
|
list_add_tail(&msg_q->list_elem, &hmsg_mgr->queue_list);
|
|
*msgq = msg_q;
|
|
/* Signal that free frames are now available */
|
|
if (!list_empty(&hmsg_mgr->msg_free_list))
|
|
sync_set_event(hmsg_mgr->sync_event);
|
|
|
|
/* Exit critical section */
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
|
|
return 0;
|
|
out_err:
|
|
delete_msg_queue(msg_q, num_allocated);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_delete ========
|
|
* Delete a msg_ctrl manager allocated in bridge_msg_create().
|
|
*/
|
|
void bridge_msg_delete(struct msg_mgr *hmsg_mgr)
|
|
{
|
|
delete_msg_mgr(hmsg_mgr);
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_delete_queue ========
|
|
* Delete a msg_ctrl queue allocated in bridge_msg_create_queue.
|
|
*/
|
|
void bridge_msg_delete_queue(struct msg_queue *msg_queue_obj)
|
|
{
|
|
struct msg_mgr *hmsg_mgr;
|
|
u32 io_msg_pend;
|
|
|
|
if (!msg_queue_obj || !msg_queue_obj->msg_mgr)
|
|
return;
|
|
|
|
hmsg_mgr = msg_queue_obj->msg_mgr;
|
|
msg_queue_obj->done = true;
|
|
/* Unblock all threads blocked in MSG_Get() or MSG_Put(). */
|
|
io_msg_pend = msg_queue_obj->io_msg_pend;
|
|
while (io_msg_pend) {
|
|
/* Unblock thread */
|
|
sync_set_event(msg_queue_obj->sync_done);
|
|
/* Wait for acknowledgement */
|
|
sync_wait_on_event(msg_queue_obj->sync_done_ack, SYNC_INFINITE);
|
|
io_msg_pend = msg_queue_obj->io_msg_pend;
|
|
}
|
|
/* Remove message queue from hmsg_mgr->queue_list */
|
|
spin_lock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
list_del(&msg_queue_obj->list_elem);
|
|
/* Free the message queue object */
|
|
delete_msg_queue(msg_queue_obj, msg_queue_obj->max_msgs);
|
|
if (list_empty(&hmsg_mgr->msg_free_list))
|
|
sync_reset_event(hmsg_mgr->sync_event);
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_get ========
|
|
* Get a message from a msg_ctrl queue.
|
|
*/
|
|
int bridge_msg_get(struct msg_queue *msg_queue_obj,
|
|
struct dsp_msg *pmsg, u32 utimeout)
|
|
{
|
|
struct msg_frame *msg_frame_obj;
|
|
struct msg_mgr *hmsg_mgr;
|
|
struct sync_object *syncs[2];
|
|
u32 index;
|
|
int status = 0;
|
|
|
|
if (!msg_queue_obj || pmsg == NULL)
|
|
return -ENOMEM;
|
|
|
|
hmsg_mgr = msg_queue_obj->msg_mgr;
|
|
|
|
spin_lock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
/* If a message is already there, get it */
|
|
if (!list_empty(&msg_queue_obj->msg_used_list)) {
|
|
msg_frame_obj = list_first_entry(&msg_queue_obj->msg_used_list,
|
|
struct msg_frame, list_elem);
|
|
list_del(&msg_frame_obj->list_elem);
|
|
*pmsg = msg_frame_obj->msg_data.msg;
|
|
list_add_tail(&msg_frame_obj->list_elem,
|
|
&msg_queue_obj->msg_free_list);
|
|
if (list_empty(&msg_queue_obj->msg_used_list))
|
|
sync_reset_event(msg_queue_obj->sync_event);
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
return 0;
|
|
}
|
|
|
|
if (msg_queue_obj->done) {
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
return -EPERM;
|
|
}
|
|
msg_queue_obj->io_msg_pend++;
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
|
|
/*
|
|
* Wait til message is available, timeout, or done. We don't
|
|
* have to schedule the DPC, since the DSP will send messages
|
|
* when they are available.
|
|
*/
|
|
syncs[0] = msg_queue_obj->sync_event;
|
|
syncs[1] = msg_queue_obj->sync_done;
|
|
status = sync_wait_on_multiple_events(syncs, 2, utimeout, &index);
|
|
|
|
spin_lock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
if (msg_queue_obj->done) {
|
|
msg_queue_obj->io_msg_pend--;
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
/*
|
|
* Signal that we're not going to access msg_queue_obj
|
|
* anymore, so it can be deleted.
|
|
*/
|
|
sync_set_event(msg_queue_obj->sync_done_ack);
|
|
return -EPERM;
|
|
}
|
|
if (!status && !list_empty(&msg_queue_obj->msg_used_list)) {
|
|
/* Get msg from used list */
|
|
msg_frame_obj = list_first_entry(&msg_queue_obj->msg_used_list,
|
|
struct msg_frame, list_elem);
|
|
list_del(&msg_frame_obj->list_elem);
|
|
/* Copy message into pmsg and put frame on the free list */
|
|
*pmsg = msg_frame_obj->msg_data.msg;
|
|
list_add_tail(&msg_frame_obj->list_elem,
|
|
&msg_queue_obj->msg_free_list);
|
|
}
|
|
msg_queue_obj->io_msg_pend--;
|
|
/* Reset the event if there are still queued messages */
|
|
if (!list_empty(&msg_queue_obj->msg_used_list))
|
|
sync_set_event(msg_queue_obj->sync_event);
|
|
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_put ========
|
|
* Put a message onto a msg_ctrl queue.
|
|
*/
|
|
int bridge_msg_put(struct msg_queue *msg_queue_obj,
|
|
const struct dsp_msg *pmsg, u32 utimeout)
|
|
{
|
|
struct msg_frame *msg_frame_obj;
|
|
struct msg_mgr *hmsg_mgr;
|
|
struct sync_object *syncs[2];
|
|
u32 index;
|
|
int status;
|
|
|
|
if (!msg_queue_obj || !pmsg || !msg_queue_obj->msg_mgr)
|
|
return -EFAULT;
|
|
|
|
hmsg_mgr = msg_queue_obj->msg_mgr;
|
|
|
|
spin_lock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
|
|
/* If a message frame is available, use it */
|
|
if (!list_empty(&hmsg_mgr->msg_free_list)) {
|
|
msg_frame_obj = list_first_entry(&hmsg_mgr->msg_free_list,
|
|
struct msg_frame, list_elem);
|
|
list_del(&msg_frame_obj->list_elem);
|
|
msg_frame_obj->msg_data.msg = *pmsg;
|
|
msg_frame_obj->msg_data.msgq_id =
|
|
msg_queue_obj->msgq_id;
|
|
list_add_tail(&msg_frame_obj->list_elem,
|
|
&hmsg_mgr->msg_used_list);
|
|
hmsg_mgr->msgs_pending++;
|
|
|
|
if (list_empty(&hmsg_mgr->msg_free_list))
|
|
sync_reset_event(hmsg_mgr->sync_event);
|
|
|
|
/* Release critical section before scheduling DPC */
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
/* Schedule a DPC, to do the actual data transfer: */
|
|
iosm_schedule(hmsg_mgr->iomgr);
|
|
return 0;
|
|
}
|
|
|
|
if (msg_queue_obj->done) {
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
return -EPERM;
|
|
}
|
|
msg_queue_obj->io_msg_pend++;
|
|
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
|
|
/* Wait til a free message frame is available, timeout, or done */
|
|
syncs[0] = hmsg_mgr->sync_event;
|
|
syncs[1] = msg_queue_obj->sync_done;
|
|
status = sync_wait_on_multiple_events(syncs, 2, utimeout, &index);
|
|
if (status)
|
|
return status;
|
|
|
|
/* Enter critical section */
|
|
spin_lock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
if (msg_queue_obj->done) {
|
|
msg_queue_obj->io_msg_pend--;
|
|
/* Exit critical section */
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
/*
|
|
* Signal that we're not going to access msg_queue_obj
|
|
* anymore, so it can be deleted.
|
|
*/
|
|
sync_set_event(msg_queue_obj->sync_done_ack);
|
|
return -EPERM;
|
|
}
|
|
|
|
if (list_empty(&hmsg_mgr->msg_free_list)) {
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Get msg from free list */
|
|
msg_frame_obj = list_first_entry(&hmsg_mgr->msg_free_list,
|
|
struct msg_frame, list_elem);
|
|
/*
|
|
* Copy message into pmsg and put frame on the
|
|
* used list.
|
|
*/
|
|
list_del(&msg_frame_obj->list_elem);
|
|
msg_frame_obj->msg_data.msg = *pmsg;
|
|
msg_frame_obj->msg_data.msgq_id = msg_queue_obj->msgq_id;
|
|
list_add_tail(&msg_frame_obj->list_elem, &hmsg_mgr->msg_used_list);
|
|
hmsg_mgr->msgs_pending++;
|
|
/*
|
|
* Schedule a DPC, to do the actual
|
|
* data transfer.
|
|
*/
|
|
iosm_schedule(hmsg_mgr->iomgr);
|
|
|
|
msg_queue_obj->io_msg_pend--;
|
|
/* Reset event if there are still frames available */
|
|
if (!list_empty(&hmsg_mgr->msg_free_list))
|
|
sync_set_event(hmsg_mgr->sync_event);
|
|
|
|
/* Exit critical section */
|
|
spin_unlock_bh(&hmsg_mgr->msg_mgr_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_register_notify ========
|
|
*/
|
|
int bridge_msg_register_notify(struct msg_queue *msg_queue_obj,
|
|
u32 event_mask, u32 notify_type,
|
|
struct dsp_notification *hnotification)
|
|
{
|
|
int status = 0;
|
|
|
|
if (!msg_queue_obj || !hnotification) {
|
|
status = -ENOMEM;
|
|
goto func_end;
|
|
}
|
|
|
|
if (!(event_mask == DSP_NODEMESSAGEREADY || event_mask == 0)) {
|
|
status = -EPERM;
|
|
goto func_end;
|
|
}
|
|
|
|
if (notify_type != DSP_SIGNALEVENT) {
|
|
status = -EBADR;
|
|
goto func_end;
|
|
}
|
|
|
|
if (event_mask)
|
|
status = ntfy_register(msg_queue_obj->ntfy_obj, hnotification,
|
|
event_mask, notify_type);
|
|
else
|
|
status = ntfy_unregister(msg_queue_obj->ntfy_obj,
|
|
hnotification);
|
|
|
|
if (status == -EINVAL) {
|
|
/* Not registered. Ok, since we couldn't have known. Node
|
|
* notifications are split between node state change handled
|
|
* by NODE, and message ready handled by msg_ctrl. */
|
|
status = 0;
|
|
}
|
|
func_end:
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* ======== bridge_msg_set_queue_id ========
|
|
*/
|
|
void bridge_msg_set_queue_id(struct msg_queue *msg_queue_obj, u32 msgq_id)
|
|
{
|
|
/*
|
|
* A message queue must be created when a node is allocated,
|
|
* so that node_register_notify() can be called before the node
|
|
* is created. Since we don't know the node environment until the
|
|
* node is created, we need this function to set msg_queue_obj->msgq_id
|
|
* to the node environment, after the node is created.
|
|
*/
|
|
if (msg_queue_obj)
|
|
msg_queue_obj->msgq_id = msgq_id;
|
|
}
|
|
|
|
/*
|
|
* ======== add_new_msg ========
|
|
* Must be called in message manager critical section.
|
|
*/
|
|
static int add_new_msg(struct list_head *msg_list)
|
|
{
|
|
struct msg_frame *pmsg;
|
|
|
|
pmsg = kzalloc(sizeof(struct msg_frame), GFP_ATOMIC);
|
|
if (!pmsg)
|
|
return -ENOMEM;
|
|
|
|
list_add_tail(&pmsg->list_elem, msg_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ======== delete_msg_mgr ========
|
|
*/
|
|
static void delete_msg_mgr(struct msg_mgr *hmsg_mgr)
|
|
{
|
|
if (!hmsg_mgr)
|
|
return;
|
|
|
|
/* FIXME: free elements from queue_list? */
|
|
free_msg_list(&hmsg_mgr->msg_free_list);
|
|
free_msg_list(&hmsg_mgr->msg_used_list);
|
|
kfree(hmsg_mgr->sync_event);
|
|
kfree(hmsg_mgr);
|
|
}
|
|
|
|
/*
|
|
* ======== delete_msg_queue ========
|
|
*/
|
|
static void delete_msg_queue(struct msg_queue *msg_queue_obj, u32 num_to_dsp)
|
|
{
|
|
struct msg_mgr *hmsg_mgr;
|
|
struct msg_frame *pmsg, *tmp;
|
|
u32 i;
|
|
|
|
if (!msg_queue_obj || !msg_queue_obj->msg_mgr)
|
|
return;
|
|
|
|
hmsg_mgr = msg_queue_obj->msg_mgr;
|
|
|
|
/* Pull off num_to_dsp message frames from Msg manager and free */
|
|
i = 0;
|
|
list_for_each_entry_safe(pmsg, tmp, &hmsg_mgr->msg_free_list,
|
|
list_elem) {
|
|
list_del(&pmsg->list_elem);
|
|
kfree(pmsg);
|
|
if (i++ >= num_to_dsp)
|
|
break;
|
|
}
|
|
|
|
free_msg_list(&msg_queue_obj->msg_free_list);
|
|
free_msg_list(&msg_queue_obj->msg_used_list);
|
|
|
|
if (msg_queue_obj->ntfy_obj) {
|
|
ntfy_delete(msg_queue_obj->ntfy_obj);
|
|
kfree(msg_queue_obj->ntfy_obj);
|
|
}
|
|
|
|
kfree(msg_queue_obj->sync_event);
|
|
kfree(msg_queue_obj->sync_done);
|
|
kfree(msg_queue_obj->sync_done_ack);
|
|
|
|
kfree(msg_queue_obj);
|
|
}
|
|
|
|
/*
|
|
* ======== free_msg_list ========
|
|
*/
|
|
static void free_msg_list(struct list_head *msg_list)
|
|
{
|
|
struct msg_frame *pmsg, *tmp;
|
|
|
|
if (!msg_list)
|
|
return;
|
|
|
|
list_for_each_entry_safe(pmsg, tmp, msg_list, list_elem) {
|
|
list_del(&pmsg->list_elem);
|
|
kfree(pmsg);
|
|
}
|
|
}
|