/* * disp.c * * DSP-BIOS Bridge driver support functions for TI OMAP processors. * * Node Dispatcher interface. Communicates with Resource Manager Server * (RMS) on DSP. Access to RMS is synchronized in NODE. * * 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> /* ----------------------------------- Host OS */ #include <dspbridge/host_os.h> /* ----------------------------------- DSP/BIOS Bridge */ #include <dspbridge/dbdefs.h> /* ----------------------------------- Trace & Debug */ #include <dspbridge/dbc.h> /* ----------------------------------- OS Adaptation Layer */ #include <dspbridge/sync.h> /* ----------------------------------- Link Driver */ #include <dspbridge/dspdefs.h> /* ----------------------------------- Platform Manager */ #include <dspbridge/dev.h> #include <dspbridge/chnldefs.h> /* ----------------------------------- Resource Manager */ #include <dspbridge/nodedefs.h> #include <dspbridge/nodepriv.h> #include <dspbridge/rms_sh.h> /* ----------------------------------- This */ #include <dspbridge/disp.h> /* Size of a reply from RMS */ #define REPLYSIZE (3 * sizeof(rms_word)) /* Reserved channel offsets for communication with RMS */ #define CHNLTORMSOFFSET 0 #define CHNLFROMRMSOFFSET 1 #define CHNLIOREQS 1 /* * ======== disp_object ======== */ struct disp_object { struct dev_object *dev_obj; /* Device for this processor */ /* Function interface to Bridge driver */ struct bridge_drv_interface *intf_fxns; struct chnl_mgr *chnl_mgr; /* Channel manager */ struct chnl_object *chnl_to_dsp; /* Chnl for commands to RMS */ struct chnl_object *chnl_from_dsp; /* Chnl for replies from RMS */ u8 *buf; /* Buffer for commands, replies */ u32 bufsize; /* buf size in bytes */ u32 bufsize_rms; /* buf size in RMS words */ u32 char_size; /* Size of DSP character */ u32 word_size; /* Size of DSP word */ u32 data_mau_size; /* Size of DSP Data MAU */ }; static u32 refs; static void delete_disp(struct disp_object *disp_obj); static int fill_stream_def(rms_word *pdw_buf, u32 *ptotal, u32 offset, struct node_strmdef strm_def, u32 max, u32 chars_in_rms_word); static int send_message(struct disp_object *disp_obj, u32 timeout, u32 ul_bytes, u32 *pdw_arg); /* * ======== disp_create ======== * Create a NODE Dispatcher object. */ int disp_create(struct disp_object **dispatch_obj, struct dev_object *hdev_obj, const struct disp_attr *disp_attrs) { struct disp_object *disp_obj; struct bridge_drv_interface *intf_fxns; u32 ul_chnl_id; struct chnl_attr chnl_attr_obj; int status = 0; u8 dev_type; DBC_REQUIRE(refs > 0); DBC_REQUIRE(dispatch_obj != NULL); DBC_REQUIRE(disp_attrs != NULL); DBC_REQUIRE(hdev_obj != NULL); *dispatch_obj = NULL; /* Allocate Node Dispatcher object */ disp_obj = kzalloc(sizeof(struct disp_object), GFP_KERNEL); if (disp_obj == NULL) status = -ENOMEM; else disp_obj->dev_obj = hdev_obj; /* Get Channel manager and Bridge function interface */ if (!status) { status = dev_get_chnl_mgr(hdev_obj, &(disp_obj->chnl_mgr)); if (!status) { (void)dev_get_intf_fxns(hdev_obj, &intf_fxns); disp_obj->intf_fxns = intf_fxns; } } /* check device type and decide if streams or messag'ing is used for * RMS/EDS */ if (status) goto func_cont; status = dev_get_dev_type(hdev_obj, &dev_type); if (status) goto func_cont; if (dev_type != DSP_UNIT) { status = -EPERM; goto func_cont; } disp_obj->char_size = DSPWORDSIZE; disp_obj->word_size = DSPWORDSIZE; disp_obj->data_mau_size = DSPWORDSIZE; /* Open channels for communicating with the RMS */ chnl_attr_obj.uio_reqs = CHNLIOREQS; chnl_attr_obj.event_obj = NULL; ul_chnl_id = disp_attrs->chnl_offset + CHNLTORMSOFFSET; status = (*intf_fxns->chnl_open) (&(disp_obj->chnl_to_dsp), disp_obj->chnl_mgr, CHNL_MODETODSP, ul_chnl_id, &chnl_attr_obj); if (!status) { ul_chnl_id = disp_attrs->chnl_offset + CHNLFROMRMSOFFSET; status = (*intf_fxns->chnl_open) (&(disp_obj->chnl_from_dsp), disp_obj->chnl_mgr, CHNL_MODEFROMDSP, ul_chnl_id, &chnl_attr_obj); } if (!status) { /* Allocate buffer for commands, replies */ disp_obj->bufsize = disp_attrs->chnl_buf_size; disp_obj->bufsize_rms = RMS_COMMANDBUFSIZE; disp_obj->buf = kzalloc(disp_obj->bufsize, GFP_KERNEL); if (disp_obj->buf == NULL) status = -ENOMEM; } func_cont: if (!status) *dispatch_obj = disp_obj; else delete_disp(disp_obj); DBC_ENSURE((status && *dispatch_obj == NULL) || (!status && *dispatch_obj)); return status; } /* * ======== disp_delete ======== * Delete the NODE Dispatcher. */ void disp_delete(struct disp_object *disp_obj) { DBC_REQUIRE(refs > 0); DBC_REQUIRE(disp_obj); delete_disp(disp_obj); } /* * ======== disp_exit ======== * Discontinue usage of DISP module. */ void disp_exit(void) { DBC_REQUIRE(refs > 0); refs--; DBC_ENSURE(refs >= 0); } /* * ======== disp_init ======== * Initialize the DISP module. */ bool disp_init(void) { bool ret = true; DBC_REQUIRE(refs >= 0); if (ret) refs++; DBC_ENSURE((ret && (refs > 0)) || (!ret && (refs >= 0))); return ret; } /* * ======== disp_node_change_priority ======== * Change the priority of a node currently running on the target. */ int disp_node_change_priority(struct disp_object *disp_obj, struct node_object *hnode, u32 rms_fxn, nodeenv node_env, s32 prio) { u32 dw_arg; struct rms_command *rms_cmd; int status = 0; DBC_REQUIRE(refs > 0); DBC_REQUIRE(disp_obj); DBC_REQUIRE(hnode != NULL); /* Send message to RMS to change priority */ rms_cmd = (struct rms_command *)(disp_obj->buf); rms_cmd->fxn = (rms_word) (rms_fxn); rms_cmd->arg1 = (rms_word) node_env; rms_cmd->arg2 = prio; status = send_message(disp_obj, node_get_timeout(hnode), sizeof(struct rms_command), &dw_arg); return status; } /* * ======== disp_node_create ======== * Create a node on the DSP by remotely calling the node's create function. */ int disp_node_create(struct disp_object *disp_obj, struct node_object *hnode, u32 rms_fxn, u32 ul_create_fxn, const struct node_createargs *pargs, nodeenv *node_env) { struct node_msgargs node_msg_args; struct node_taskargs task_arg_obj; struct rms_command *rms_cmd; struct rms_msg_args *pmsg_args; struct rms_more_task_args *more_task_args; enum node_type node_type; u32 dw_length; rms_word *pdw_buf = NULL; u32 ul_bytes; u32 i; u32 total; u32 chars_in_rms_word; s32 task_args_offset; s32 sio_in_def_offset; s32 sio_out_def_offset; s32 sio_defs_offset; s32 args_offset = -1; s32 offset; struct node_strmdef strm_def; u32 max; int status = 0; struct dsp_nodeinfo node_info; u8 dev_type; DBC_REQUIRE(refs > 0); DBC_REQUIRE(disp_obj); DBC_REQUIRE(hnode != NULL); DBC_REQUIRE(node_get_type(hnode) != NODE_DEVICE); DBC_REQUIRE(node_env != NULL); status = dev_get_dev_type(disp_obj->dev_obj, &dev_type); if (status) goto func_end; if (dev_type != DSP_UNIT) { dev_dbg(bridge, "%s: unknown device type = 0x%x\n", __func__, dev_type); goto func_end; } DBC_REQUIRE(pargs != NULL); node_type = node_get_type(hnode); node_msg_args = pargs->asa.node_msg_args; max = disp_obj->bufsize_rms; /*Max # of RMS words that can be sent */ DBC_ASSERT(max == RMS_COMMANDBUFSIZE); chars_in_rms_word = sizeof(rms_word) / disp_obj->char_size; /* Number of RMS words needed to hold arg data */ dw_length = (node_msg_args.arg_length + chars_in_rms_word - 1) / chars_in_rms_word; /* Make sure msg args and command fit in buffer */ total = sizeof(struct rms_command) / sizeof(rms_word) + sizeof(struct rms_msg_args) / sizeof(rms_word) - 1 + dw_length; if (total >= max) { status = -EPERM; dev_dbg(bridge, "%s: Message args too large for buffer! size " "= %d, max = %d\n", __func__, total, max); } /* * Fill in buffer to send to RMS. * The buffer will have the following format: * * RMS command: * Address of RMS_CreateNode() * Address of node's create function * dummy argument * node type * * Message Args: * max number of messages * segid for message buffer allocation * notification type to use when message is received * length of message arg data * message args data * * Task Args (if task or socket node): * priority * stack size * system stack size * stack segment * misc * number of input streams * pSTRMInDef[] - offsets of STRM definitions for input streams * number of output streams * pSTRMOutDef[] - offsets of STRM definitions for output * streams * STRMInDef[] - array of STRM definitions for input streams * STRMOutDef[] - array of STRM definitions for output streams * * Socket Args (if DAIS socket node): * */ if (!status) { total = 0; /* Total number of words in buffer so far */ pdw_buf = (rms_word *) disp_obj->buf; rms_cmd = (struct rms_command *)pdw_buf; rms_cmd->fxn = (rms_word) (rms_fxn); rms_cmd->arg1 = (rms_word) (ul_create_fxn); if (node_get_load_type(hnode) == NLDR_DYNAMICLOAD) { /* Flush ICACHE on Load */ rms_cmd->arg2 = 1; /* dummy argument */ } else { /* Do not flush ICACHE */ rms_cmd->arg2 = 0; /* dummy argument */ } rms_cmd->data = node_get_type(hnode); /* * args_offset is the offset of the data field in struct * rms_command structure. We need this to calculate stream * definition offsets. */ args_offset = 3; total += sizeof(struct rms_command) / sizeof(rms_word); /* Message args */ pmsg_args = (struct rms_msg_args *)(pdw_buf + total); pmsg_args->max_msgs = node_msg_args.max_msgs; pmsg_args->segid = node_msg_args.seg_id; pmsg_args->notify_type = node_msg_args.notify_type; pmsg_args->arg_length = node_msg_args.arg_length; total += sizeof(struct rms_msg_args) / sizeof(rms_word) - 1; memcpy(pdw_buf + total, node_msg_args.pdata, node_msg_args.arg_length); total += dw_length; } if (status) goto func_end; /* If node is a task node, copy task create arguments into buffer */ if (node_type == NODE_TASK || node_type == NODE_DAISSOCKET) { task_arg_obj = pargs->asa.task_arg_obj; task_args_offset = total; total += sizeof(struct rms_more_task_args) / sizeof(rms_word) + 1 + task_arg_obj.num_inputs + task_arg_obj.num_outputs; /* Copy task arguments */ if (total < max) { total = task_args_offset; more_task_args = (struct rms_more_task_args *)(pdw_buf + total); /* * Get some important info about the node. Note that we * don't just reach into the hnode struct because * that would break the node object's abstraction. */ get_node_info(hnode, &node_info); more_task_args->priority = node_info.execution_priority; more_task_args->stack_size = task_arg_obj.stack_size; more_task_args->sysstack_size = task_arg_obj.sys_stack_size; more_task_args->stack_seg = task_arg_obj.stack_seg; more_task_args->heap_addr = task_arg_obj.dsp_heap_addr; more_task_args->heap_size = task_arg_obj.heap_size; more_task_args->misc = task_arg_obj.dais_arg; more_task_args->num_input_streams = task_arg_obj.num_inputs; total += sizeof(struct rms_more_task_args) / sizeof(rms_word); dev_dbg(bridge, "%s: dsp_heap_addr %x, heap_size %x\n", __func__, task_arg_obj.dsp_heap_addr, task_arg_obj.heap_size); /* Keep track of pSIOInDef[] and pSIOOutDef[] * positions in the buffer, since this needs to be * filled in later. */ sio_in_def_offset = total; total += task_arg_obj.num_inputs; pdw_buf[total++] = task_arg_obj.num_outputs; sio_out_def_offset = total; total += task_arg_obj.num_outputs; sio_defs_offset = total; /* Fill SIO defs and offsets */ offset = sio_defs_offset; for (i = 0; i < task_arg_obj.num_inputs; i++) { if (status) break; pdw_buf[sio_in_def_offset + i] = (offset - args_offset) * (sizeof(rms_word) / DSPWORDSIZE); strm_def = task_arg_obj.strm_in_def[i]; status = fill_stream_def(pdw_buf, &total, offset, strm_def, max, chars_in_rms_word); offset = total; } for (i = 0; (i < task_arg_obj.num_outputs) && (!status); i++) { pdw_buf[sio_out_def_offset + i] = (offset - args_offset) * (sizeof(rms_word) / DSPWORDSIZE); strm_def = task_arg_obj.strm_out_def[i]; status = fill_stream_def(pdw_buf, &total, offset, strm_def, max, chars_in_rms_word); offset = total; } } else { /* Args won't fit */ status = -EPERM; } } if (!status) { ul_bytes = total * sizeof(rms_word); DBC_ASSERT(ul_bytes < (RMS_COMMANDBUFSIZE * sizeof(rms_word))); status = send_message(disp_obj, node_get_timeout(hnode), ul_bytes, node_env); } func_end: return status; } /* * ======== disp_node_delete ======== * purpose: * Delete a node on the DSP by remotely calling the node's delete function. * */ int disp_node_delete(struct disp_object *disp_obj, struct node_object *hnode, u32 rms_fxn, u32 ul_delete_fxn, nodeenv node_env) { u32 dw_arg; struct rms_command *rms_cmd; int status = 0; u8 dev_type; DBC_REQUIRE(refs > 0); DBC_REQUIRE(disp_obj); DBC_REQUIRE(hnode != NULL); status = dev_get_dev_type(disp_obj->dev_obj, &dev_type); if (!status) { if (dev_type == DSP_UNIT) { /* * Fill in buffer to send to RMS */ rms_cmd = (struct rms_command *)disp_obj->buf; rms_cmd->fxn = (rms_word) (rms_fxn); rms_cmd->arg1 = (rms_word) node_env; rms_cmd->arg2 = (rms_word) (ul_delete_fxn); rms_cmd->data = node_get_type(hnode); status = send_message(disp_obj, node_get_timeout(hnode), sizeof(struct rms_command), &dw_arg); } } return status; } /* * ======== disp_node_run ======== * purpose: * Start execution of a node's execute phase, or resume execution of a node * that has been suspended (via DISP_NodePause()) on the DSP. */ int disp_node_run(struct disp_object *disp_obj, struct node_object *hnode, u32 rms_fxn, u32 ul_execute_fxn, nodeenv node_env) { u32 dw_arg; struct rms_command *rms_cmd; int status = 0; u8 dev_type; DBC_REQUIRE(refs > 0); DBC_REQUIRE(disp_obj); DBC_REQUIRE(hnode != NULL); status = dev_get_dev_type(disp_obj->dev_obj, &dev_type); if (!status) { if (dev_type == DSP_UNIT) { /* * Fill in buffer to send to RMS. */ rms_cmd = (struct rms_command *)disp_obj->buf; rms_cmd->fxn = (rms_word) (rms_fxn); rms_cmd->arg1 = (rms_word) node_env; rms_cmd->arg2 = (rms_word) (ul_execute_fxn); rms_cmd->data = node_get_type(hnode); status = send_message(disp_obj, node_get_timeout(hnode), sizeof(struct rms_command), &dw_arg); } } return status; } /* * ======== delete_disp ======== * purpose: * Frees the resources allocated for the dispatcher. */ static void delete_disp(struct disp_object *disp_obj) { int status = 0; struct bridge_drv_interface *intf_fxns; if (disp_obj) { intf_fxns = disp_obj->intf_fxns; /* Free Node Dispatcher resources */ if (disp_obj->chnl_from_dsp) { /* Channel close can fail only if the channel handle * is invalid. */ status = (*intf_fxns->chnl_close) (disp_obj->chnl_from_dsp); if (status) { dev_dbg(bridge, "%s: Failed to close channel " "from RMS: 0x%x\n", __func__, status); } } if (disp_obj->chnl_to_dsp) { status = (*intf_fxns->chnl_close) (disp_obj-> chnl_to_dsp); if (status) { dev_dbg(bridge, "%s: Failed to close channel to" " RMS: 0x%x\n", __func__, status); } } kfree(disp_obj->buf); kfree(disp_obj); } } /* * ======== fill_stream_def ======== * purpose: * Fills stream definitions. */ static int fill_stream_def(rms_word *pdw_buf, u32 *ptotal, u32 offset, struct node_strmdef strm_def, u32 max, u32 chars_in_rms_word) { struct rms_strm_def *strm_def_obj; u32 total = *ptotal; u32 name_len; u32 dw_length; int status = 0; if (total + sizeof(struct rms_strm_def) / sizeof(rms_word) >= max) { status = -EPERM; } else { strm_def_obj = (struct rms_strm_def *)(pdw_buf + total); strm_def_obj->bufsize = strm_def.buf_size; strm_def_obj->nbufs = strm_def.num_bufs; strm_def_obj->segid = strm_def.seg_id; strm_def_obj->align = strm_def.buf_alignment; strm_def_obj->timeout = strm_def.timeout; } if (!status) { /* * Since we haven't added the device name yet, subtract * 1 from total. */ total += sizeof(struct rms_strm_def) / sizeof(rms_word) - 1; DBC_REQUIRE(strm_def.sz_device); dw_length = strlen(strm_def.sz_device) + 1; /* Number of RMS_WORDS needed to hold device name */ name_len = (dw_length + chars_in_rms_word - 1) / chars_in_rms_word; if (total + name_len >= max) { status = -EPERM; } else { /* * Zero out last word, since the device name may not * extend to completely fill this word. */ pdw_buf[total + name_len - 1] = 0; /** TODO USE SERVICES * */ memcpy(pdw_buf + total, strm_def.sz_device, dw_length); total += name_len; *ptotal = total; } } return status; } /* * ======== send_message ====== * Send command message to RMS, get reply from RMS. */ static int send_message(struct disp_object *disp_obj, u32 timeout, u32 ul_bytes, u32 *pdw_arg) { struct bridge_drv_interface *intf_fxns; struct chnl_object *chnl_obj; u32 dw_arg = 0; u8 *pbuf; struct chnl_ioc chnl_ioc_obj; int status = 0; DBC_REQUIRE(pdw_arg != NULL); *pdw_arg = (u32) NULL; intf_fxns = disp_obj->intf_fxns; chnl_obj = disp_obj->chnl_to_dsp; pbuf = disp_obj->buf; /* Send the command */ status = (*intf_fxns->chnl_add_io_req) (chnl_obj, pbuf, ul_bytes, 0, 0L, dw_arg); if (status) goto func_end; status = (*intf_fxns->chnl_get_ioc) (chnl_obj, timeout, &chnl_ioc_obj); if (!status) { if (!CHNL_IS_IO_COMPLETE(chnl_ioc_obj)) { if (CHNL_IS_TIMED_OUT(chnl_ioc_obj)) status = -ETIME; else status = -EPERM; } } /* Get the reply */ if (status) goto func_end; chnl_obj = disp_obj->chnl_from_dsp; ul_bytes = REPLYSIZE; status = (*intf_fxns->chnl_add_io_req) (chnl_obj, pbuf, ul_bytes, 0, 0L, dw_arg); if (status) goto func_end; status = (*intf_fxns->chnl_get_ioc) (chnl_obj, timeout, &chnl_ioc_obj); if (!status) { if (CHNL_IS_TIMED_OUT(chnl_ioc_obj)) { status = -ETIME; } else if (chnl_ioc_obj.byte_size < ul_bytes) { /* Did not get all of the reply from the RMS */ status = -EPERM; } else { if (CHNL_IS_IO_COMPLETE(chnl_ioc_obj)) { DBC_ASSERT(chnl_ioc_obj.buf == pbuf); if (*((int *)chnl_ioc_obj.buf) < 0) { /* Translate DSP's to kernel error */ status = -EREMOTEIO; dev_dbg(bridge, "%s: DSP-side failed:" " DSP errcode = 0x%x, Kernel " "errcode = %d\n", __func__, *(int *)pbuf, status); } *pdw_arg = (((rms_word *) (chnl_ioc_obj.buf))[1]); } else { status = -EPERM; } } } func_end: return status; }