/* * 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 /* ----------------------------------- DSP/BIOS Bridge */ #include /* ----------------------------------- Trace & Debug */ #include /* ----------------------------------- OS Adaptation Layer */ #include /* ----------------------------------- Platform Manager */ #include /* ----------------------------------- Others */ #include /* ----------------------------------- This */ #include <_msg_sm.h> #include /* ----------------------------------- 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) { if (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); } }