| /* |
| * 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); |
| } |
| } |