/** * Push RX pdu for any modem command * */ static int dlp_ctrl_push_rx_pdu(struct dlp_channel *ch_ctx) { int ret; struct hsi_msg *rx_msg; struct dlp_command *dlp_cmd; /* Allocate the DLP command */ dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, DLP_CMD_NOP, 0, 0, 0); if (!dlp_cmd) { pr_err(DRVNAME ": Out of memory (rx_pdu)\n"); ret = -ENOMEM; goto out; } /* Allocate a new RX msg */ rx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL, HSI_MSG_READ, DLP_CTRL_RX_PDU_SIZE, 1, dlp_cmd, dlp_ctrl_complete_rx, dlp_ctrl_msg_destruct); if (!rx_msg) { pr_err(DRVNAME ": dlp_pdu_alloc() failed\n"); ret = -ENOMEM; goto free_cmd; } /* Send the RX HSI msg */ ret = hsi_async(rx_msg->cl, rx_msg); if (ret) { pr_err(DRVNAME ": RX push failed, ret:%d\n", ret); ret = -EIO; goto free_msg; } return 0; free_msg: /* Free the msg */ dlp_pdu_free(rx_msg, rx_msg->channel); free_cmd: /* Delete the command */ kfree(dlp_cmd); out: return ret; }
/* * Push RX pdu on channel 0 * */ static int dlp_flash_push_rx_pdu(struct dlp_channel *ch_ctx) { int ret; struct hsi_msg *rx_msg; PROLOG(); /* Allocate a new RX msg */ rx_msg = dlp_pdu_alloc(ch_ctx->hsi_channel, HSI_MSG_READ, DLP_FLASH_RX_PDU_SIZE, 1, ch_ctx, dlp_flash_complete_rx, dlp_flash_msg_destruct_fix); if (!rx_msg) { CRITICAL("dlp_pdu_alloc(RX) failed"); ret = -ENOMEM; goto out; } /* Send the RX HSI msg */ ret = hsi_async(rx_msg->cl, rx_msg); if (ret) { CRITICAL("hsi_async() failed, ret:%d", ret); ret = -EIO; goto free_msg; } EPILOG(); return 0; free_msg: /* Free the msg */ dlp_pdu_free(rx_msg, rx_msg->channel); out: EPILOG(); return ret; }
/* * Push RX pdu on channel 0 * */ static int dlp_trace_push_rx_pdu(struct dlp_channel *ch_ctx) { int ret; struct hsi_msg *rx_msg; /* Allocate a new RX msg */ rx_msg = dlp_pdu_alloc(ch_ctx->hsi_channel, HSI_MSG_READ, DLP_TRACE_RX_PDU_SIZE, 1, ch_ctx, dlp_trace_complete_rx, dlp_trace_msg_destruct); if (!rx_msg) { pr_err(DRVNAME": dlp_pdu_alloc(RX) failed\n"); ret = -ENOMEM; goto out; } /* Send the RX HSI msg */ ret = hsi_async(rx_msg->cl, rx_msg); if (ret) { pr_err(DRVNAME": hsi_async() failed, ret:%d\n", ret); ret = -EIO; goto free_msg; } return 0; free_msg: /* Free the msg */ dlp_pdu_free(rx_msg, rx_msg->channel); out: return ret; }
/** * Send an HSI msg AND wait for the status * * @ch_ctx: a reference to the channel context to use * @id: the DLP command id * @response_id: the expected response id * @interm_state: the intermidiate state (to be set when TX is OK) * @final_state: the final state (to be set when RX (response) is OK) * @param1: the DLP command params * @param2: the DLP command params * @param3: the DLP command params * * This function blocks the caller until a response is received * or the timeout expires */ static int dlp_ctrl_cmd_send(struct dlp_channel *ch_ctx, unsigned char id, unsigned char response_id, unsigned char interm_state, unsigned char final_state, unsigned char param1, unsigned char param2, unsigned char param3) { int ret = 0, old_state; struct dlp_ctrl_context *ctrl_ctx; struct dlp_command *dlp_cmd; struct dlp_command_params expected_resp; struct hsi_msg *tx_msg = NULL; /* Check the link readiness (TTY still opened) */ if (!dlp_tty_is_link_valid()) { if (EDLP_CTRL_TX_DATA_REPORT) pr_debug(DRVNAME ": CH%d (HSI CH%d) cmd 0x%X ignored (close:%d, Time out: %d)\n", ch_ctx->ch_id, ch_ctx->hsi_channel, id, dlp_drv.tty_closed, dlp_drv.tx_timeout); return ret; } ctrl_ctx = DLP_CTRL_CTX; mutex_lock(&ch_ctx->tx.cmd_sync); /* Save the current channel state */ old_state = dlp_ctrl_get_channel_state(ch_ctx->hsi_channel); /* Backup RX callback */ dlp_save_rx_callbacks(&ctrl_ctx->ehandler); /* Allocate the DLP command */ dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, id, param1, param2, param3); if (!dlp_cmd) { pr_err(DRVNAME ": Out of memory (dlp_cmd: 0x%X)\n", id); ret = -ENOMEM; goto out; } /* Allocate a new TX msg */ tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL, HSI_MSG_WRITE, DLP_CTRL_TX_PDU_SIZE, 1, dlp_cmd, dlp_ctrl_complete_tx, dlp_ctrl_msg_destruct); if (!tx_msg) { pr_err(DRVNAME ": dlp_pdu_alloc(TX) failed\n"); ret = -ENOMEM; goto free_cmd; } /* Copy the command data */ memcpy(sg_virt(tx_msg->sgt.sgl), &dlp_cmd->params, sizeof(struct dlp_command_params)); /* Send the TX HSI msg */ ret = hsi_async(tx_msg->cl, tx_msg); if (ret) { pr_err(DRVNAME ": Unable to send 0x%X cmd (ret:%d)\n", dlp_cmd->params.id, ret); /* Free the TX msg */ dlp_pdu_free(tx_msg, tx_msg->channel); goto free_cmd; } /* Dump the TX command */ if (EDLP_CTRL_TX_DATA_REPORT) pr_debug(DRVNAME ": CTRL_TX (0x%X)\n", *((u32 *)&dlp_cmd->params)); /* Wait for TX msg to be sent */ ret = wait_for_completion_timeout(&ch_ctx->tx.cmd_xfer_done, msecs_to_jiffies(DLP_CMD_TX_TIMOUT)); if (ret == 0) { pr_err(DRVNAME ": hsi_ch:%d, cmd:0x%X => TX timeout\n", dlp_cmd->params.channel, dlp_cmd->params.id); /* No need to call the complete sending call back, * because of failure */ tx_msg->complete = NULL; tx_msg->context = NULL; ret = -EIO; /* free only the cmd, because * the message is already in the controller fifo. * It will be freed when the controller fifo will be * flushed/freed */ goto free_cmd; } /* TX msg sent, check the status */ if (dlp_cmd->status) { pr_err(DRVNAME ": Failed to send cmd:0x%X\n", dlp_cmd->params.id); ret = -EIO; /* free only the command because * the message has been already freed by the complete_tx * callback */ goto free_cmd; } /* TX OK */ /* 1. Set the intermidiate channel state */ if (interm_state != DLP_CH_STATE_NONE) dlp_ctrl_set_channel_state(ch_ctx->hsi_channel, interm_state); /* Wait for response ? */ if (response_id == DLP_CMD_NONE) { ret = 0; goto no_resp; } /* 2. Wait for the response */ ret = wait_for_completion_timeout(&ch_ctx->rx.cmd_xfer_done, msecs_to_jiffies(DLP_CMD_RX_TIMOUT)); if (ret == 0) { pr_err(DRVNAME ": hsi_ch:%d, cmd:0x%X => RX timeout\n", dlp_cmd->params.channel, dlp_cmd->params.id); ret = -EIO; goto free_cmd; } /* Set the expected response params */ expected_resp.id = response_id; expected_resp.channel = ch_ctx->hsi_channel; switch (id) { case DLP_CMD_CLOSE_CONN: expected_resp.data1 = param3; expected_resp.data2 = param2; expected_resp.data3 = CMD_ID(param1, id); break; case DLP_CMD_OPEN_CONN: default: expected_resp.data1 = param1; expected_resp.data2 = param2; expected_resp.data3 = CMD_ID(param3, id); } /* Check the received response params */ ret = 0; if (memcmp(&ctrl_ctx->response.params, &expected_resp, sizeof(expected_resp))) { pr_err(DRVNAME": cmd 0x%X unexpected response 0x%X (expected 0x%X)", id, (unsigned int)(*(u32 *)&ctrl_ctx->response.params), (unsigned int)(*(u32 *)(&expected_resp))); tx_msg->context = NULL; ret = -EIO; goto free_cmd; } no_resp: /* Response received & OK => set the new channel state */ if (final_state != DLP_CH_STATE_NONE) dlp_ctrl_set_channel_state(ch_ctx->hsi_channel, final_state); free_cmd: /* Free the DLP command */ dlp_ctrl_cmd_free(dlp_cmd); out: /* Restore RX callback */ dlp_restore_rx_callbacks(&ctrl_ctx->ehandler); /* Restore the channel state in error case */ if (ret) dlp_ctrl_set_channel_state(ch_ctx->hsi_channel, old_state); mutex_unlock(&ch_ctx->tx.cmd_sync); return ret; }
static void dlp_ctrl_complete_rx(struct hsi_msg *msg) { struct dlp_channel *ch_ctx; struct dlp_ctrl_context *ctrl_ctx; struct dlp_command *dlp_cmd = msg->context; struct dlp_command_params params, tx_params; unsigned long flags; int hsi_channel, elp_channel, ret, response, msg_complete, state; params.channel = 0; /* To please KlocWork */ /* Check the link readiness (TTY still opened) */ if (!dlp_tty_is_link_valid()) { if (EDLP_CTRL_RX_DATA_REPORT) pr_debug(DRVNAME ": CTRL: CH%d RX PDU ignored (close:%d, Time out: %d)\n", params.channel, dlp_drv.tty_closed, dlp_drv.tx_timeout); /* Delete the received msg */ dlp_pdu_free(msg, msg->channel); /* Delete the command */ dlp_ctrl_cmd_free(dlp_cmd); return; } /* Copy the reponse */ memcpy(¶ms, sg_virt(msg->sgt.sgl), sizeof(struct dlp_command_params)); ctrl_ctx = DLP_CTRL_CTX; response = -1; memcpy(&tx_params, ¶ms, sizeof(struct dlp_command_params)); msg_complete = (msg->status == HSI_STATUS_COMPLETED); /* Dump the RX command */ if (EDLP_CTRL_RX_DATA_REPORT) pr_debug(DRVNAME ": CTRL_RX (0x%X)\n", *((u32 *)¶ms)); hsi_channel = params.channel; if ((hsi_channel < 0) || (hsi_channel >= DLP_CHANNEL_COUNT)) { pr_err(DRVNAME ": Invalid HSI channel id (%d)\n", hsi_channel); goto push_rx; } elp_channel = dlp_drv.channels_hsi[hsi_channel].edlp_channel; ch_ctx = DLP_CHANNEL_CTX(elp_channel); switch (params.id) { case DLP_CMD_NOP: break; case DLP_CMD_CREDITS: if (!msg_complete) break; /* Increase the CREDITS counter */ spin_lock_irqsave(&ch_ctx->lock, flags); ch_ctx->credits += params.data3; ret = ch_ctx->credits; spin_unlock_irqrestore(&ch_ctx->lock, flags); /* Credits available ==> Notify the channel */ if (ch_ctx->credits_available_cb) ch_ctx->credits_available_cb(ch_ctx); /* CREDITS => ACK */ response = DLP_CMD_ACK; /* Set the response params */ tx_params.data1 = params.data3; tx_params.data2 = 0; tx_params.data3 = 0; break; case DLP_CMD_OPEN_CONN: if (!msg_complete) break; /* Ready ? if not, just save the OPEN_CONN request params */ spin_lock_irqsave(&ctrl_ctx->open_lock, flags); state = dlp_ctrl_get_channel_state(hsi_channel); if ((state != DLP_CH_STATE_OPENING) && (state != DLP_CH_STATE_OPENED)) { struct dlp_hsi_channel *hsi_ch; hsi_ch = &dlp_drv.channels_hsi[hsi_channel]; memcpy(&hsi_ch->open_conn, ¶ms, sizeof(params)); spin_unlock_irqrestore(&ctrl_ctx->open_lock, flags); response = -1; pr_debug(DRVNAME ": HSI CH%d OPEN_CONN received (postponed) when state is %d\n", params.channel, state); goto push_rx; } else spin_unlock_irqrestore(&ctrl_ctx->open_lock, flags); pr_debug(DRVNAME ": HSI CH%d OPEN_CONN received (size: %d)\n", params.channel, (params.data2 << 8) | params.data1); /* Check the PDU size */ response = dlp_ctrl_check_pdu_size(ch_ctx, ¶ms, &tx_params); break; case DLP_CMD_CLOSE_CONN: /* CLOSE_CONN => ACK */ response = DLP_CMD_ACK; /* Set the response params */ tx_params.data1 = params.data3; tx_params.data2 = 0; tx_params.data3 = CMD_ID(params.data3, params.id); pr_debug(DRVNAME ": ch%d close_conn received\n", params.channel); break; default: /* Save the modem response */ ctrl_ctx->response.status = (msg_complete ? 0 : -EIO); memcpy(&ctrl_ctx->response.params, ¶ms, sizeof(struct dlp_command_params)); /* Command done, notify the sender */ complete(&ch_ctx->rx.cmd_xfer_done); break; } /* Any response to send ? */ if (response == -1) goto push_rx; dlp_ctrl_send_response(ch_ctx, &tx_params, response); push_rx: /* Push the RX msg again for futur response */ ret = hsi_async(msg->cl, msg); if (ret) { pr_err(DRVNAME ": RX push failed, ret:%d\n", ret); /* We have A BIG PROBLEM if the RX msg cant be */ /* pushed again in the controller ==> */ /* No response could be received (FIFO empty) */ /* Delete the received msg */ dlp_pdu_free(msg, msg->channel); /* Delete the command */ dlp_ctrl_cmd_free(dlp_cmd); } }
static int dlp_ctrl_send_response(struct dlp_channel *ch_ctx, struct dlp_command_params *tx_params, int response) { int ret, state; struct hsi_msg *tx_msg; struct dlp_command *dlp_cmd; unsigned long flags; /* Allocate the eDLP response */ dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, response, tx_params->data1, tx_params->data2, tx_params->data3); if (!dlp_cmd) { pr_err(DRVNAME ": Out of memory (dlp_cmd: 0x%X)\n", response); return -ENOMEM; } /* Allocate a new TX msg */ tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL, HSI_MSG_WRITE, DLP_CTRL_TX_PDU_SIZE, 1, dlp_cmd, dlp_ctrl_complete_tx_async, dlp_ctrl_msg_destruct); if (!tx_msg) { pr_err(DRVNAME ": TX alloc failed\n"); /* Delete the command */ kfree(dlp_cmd); return -ENOMEM; } /* Copy the command data */ memcpy(sg_virt(tx_msg->sgt.sgl), &dlp_cmd->params, sizeof(struct dlp_command_params)); spin_lock_irqsave(&ch_ctx->lock, flags); state = dlp_ctrl_get_channel_state(ch_ctx->ch_id); if ((state != DLP_CH_STATE_OPENING) && (state != DLP_CH_STATE_OPENED) && (ch_ctx->ch_id != DLP_CHANNEL_TRACE)) spin_unlock_irqrestore(&ch_ctx->lock, flags); else { spin_unlock_irqrestore(&ch_ctx->lock, flags); /* Send the TX HSI msg */ ret = hsi_async(tx_msg->cl, tx_msg); if (ret) pr_err(DRVNAME ": TX xfer failed ! (cmd:0x%X, ret:%d)\n", dlp_cmd->params.id, ret); else { /* Dump the TX command */ if (EDLP_CTRL_TX_DATA_REPORT) pr_debug(DRVNAME ": CTRL_TX (0x%X)\n", *((u32 *)&dlp_cmd->params)); return 0; } } /* Free the TX msg */ dlp_pdu_free(tx_msg, tx_msg->channel); /* Delete the command */ kfree(dlp_cmd); return 0; }