From 08c604f897913a3d4734176025b15213930ded9e Mon Sep 17 00:00:00 2001 From: Faouaz TENOUTIT Date: Mon, 27 Feb 2012 19:55:46 +0100 Subject: [PATCH] HSI eDLP protcol delivery (20-24 February INTEG CAMP outcome) BZ: 24995 - Fix the boot crash/freeze (lock/unlock inconsistancy) - Improve the modem reset mngt (Wait for modem reset) - Remove the debug capability to the HSI controller - Put the HSI Power management under DLP switch Faouaz TENOUTIT Change-Id: I24e4fc288ce81d28e9b95f48b076f3ea6ae77a89 Signed-off-by: Faouaz TENOUTIT Reviewed-on: http://android.intel.com:8080/36786 Reviewed-by: Predon, Frederic Reviewed-by: Roulliere, Pierre Reviewed-by: Lebsir, SamiX Tested-by: Lebsir, SamiX Reviewed-by: buildbot Tested-by: buildbot --- arch/x86/configs/i386_ctp_defconfig | 14 +- arch/x86/platform/intel-mid/board-ctp.c | 103 +- drivers/hsi/clients/Kconfig | 62 ++ drivers/hsi/clients/Makefile | 4 +- drivers/hsi/clients/dlp_ctrl.c | 1255 +++++++++++++++++++++ drivers/hsi/clients/dlp_debug.c | 215 ++++ drivers/hsi/clients/dlp_main.c | 1817 +++++++++++++++++++++++++++++++ drivers/hsi/clients/dlp_main.h | 505 +++++++++ drivers/hsi/clients/dlp_net.c | 928 ++++++++++++++++ drivers/hsi/clients/dlp_tty.c | 1621 +++++++++++++++++++++++++++ drivers/hsi/controllers/intel_mid_hsi.c | 5 + drivers/hsi/dlp_debug.h | 172 +++ include/linux/hsi/hsi_dlp.h | 270 +++++ 13 files changed, 6960 insertions(+), 11 deletions(-) create mode 100644 drivers/hsi/clients/dlp_ctrl.c create mode 100644 drivers/hsi/clients/dlp_debug.c create mode 100644 drivers/hsi/clients/dlp_main.c create mode 100644 drivers/hsi/clients/dlp_main.h create mode 100644 drivers/hsi/clients/dlp_net.c create mode 100644 drivers/hsi/clients/dlp_tty.c create mode 100644 drivers/hsi/dlp_debug.h create mode 100644 include/linux/hsi/hsi_dlp.h diff --git a/arch/x86/configs/i386_ctp_defconfig b/arch/x86/configs/i386_ctp_defconfig index 22a1cdd..919fb77 100644 --- a/arch/x86/configs/i386_ctp_defconfig +++ b/arch/x86/configs/i386_ctp_defconfig @@ -1370,13 +1370,13 @@ CONFIG_HSI_ARASAN_CONFIG=y # HSI clients # # CONFIG_HSI_CHAR is not set -CONFIG_HSI_FFL_TTY=y -CONFIG_HSI_FFL_TTY_NAME="IFX" -CONFIG_HSI_FFL_TTY_FRAME_LENGTH=4096 -CONFIG_HSI_FFL_TTY_HEADER_LENGTH=4 -CONFIG_HSI_FFL_ENSURE_LAST_WORD_NULL=y -CONFIG_HSI_FFL_TTY_CHANNEL=1 -CONFIG_HSI_FFL_TTY_STATS=y +# CONFIG_HSI_FFL_TTY is not set +CONFIG_HSI_DLP=y +CONFIG_HSI_DLP_TTY_NAME="IFX" +CONFIG_HSI_DLP_TTY_STATS=y +CONFIG_HSI_DLP_NET_NAME="rmnet" +CONFIG_HSI_DLP_FRAME_LENGTH=4096 +CONFIG_HSI_DLP_HEADER_LENGTH=4 # diff --git a/arch/x86/platform/intel-mid/board-ctp.c b/arch/x86/platform/intel-mid/board-ctp.c index 93a195f..49700c6 100644 --- a/arch/x86/platform/intel-mid/board-ctp.c +++ b/arch/x86/platform/intel-mid/board-ctp.c @@ -447,9 +447,21 @@ static void *hsi_modem_platform_data(void *data) int fcdp_rb = get_gpio_by_name("modem-gpio2"); static const char hsi_char_name[] = "hsi_char"; +#if defined(CONFIG_HSI_FFL_TTY) static const char hsi_ffl_name[] = "hsi-ffl"; +#elif defined(CONFIG_HSI_DLP) + static const char hsi_dlp_name[] = "hsi-dlp"; +#endif - static struct hsi_board_info hsi_info[2] = { +#if ((defined CONFIG_HSI_FFL_TTY) && (defined CONFIG_HSI_DLP)) +#error "Only define one of HSI_FFL_TTY or HSI_DLP" +#elif (defined(CONFIG_HSI_FFL_TTY) || defined(CONFIG_HSI_DLP)) +#define HSI_CLIENT_CNT 2 +#else +#define HSI_CLIENT_CNT 1 +#endif + + static struct hsi_board_info hsi_info[HSI_CLIENT_CNT] = { [0] = { .name = hsi_char_name, .hsi_id = 0, @@ -463,6 +475,7 @@ static void *hsi_modem_platform_data(void *data) .rx_cfg.mode = HSI_MODE_FRAME, .rx_cfg.channels = 8 }, +#if defined(CONFIG_HSI_FFL_TTY) [1] = { .name = hsi_ffl_name, .hsi_id = 0, @@ -476,8 +489,24 @@ static void *hsi_modem_platform_data(void *data) .rx_cfg.mode = HSI_MODE_FRAME, .rx_cfg.channels = 8 } +#elif defined(CONFIG_HSI_DLP) + [1] = { + .name = hsi_dlp_name, + .hsi_id = 0, + .port = 0, + .archdata = NULL, + .tx_cfg.speed = 100000, /* tx clock, kHz */ + .tx_cfg.channels = 8, + .tx_cfg.mode = HSI_MODE_FRAME, + .tx_cfg.arb_mode = HSI_ARB_RR, + .rx_cfg.flow = HSI_FLOW_SYNC, + .rx_cfg.mode = HSI_MODE_FRAME, + .rx_cfg.channels = 8 + } +#endif }; +#if defined(CONFIG_HSI_FFL_TTY) static struct hsi_mid_platform_data mid_info = { .tx_dma_channels[0] = -1, .tx_dma_channels[1] = 5, @@ -487,6 +516,14 @@ static void *hsi_modem_platform_data(void *data) .tx_dma_channels[5] = -1, .tx_dma_channels[6] = -1, .tx_dma_channels[7] = -1, + .tx_sg_entries[0] = 1, + .tx_sg_entries[1] = 1, + .tx_sg_entries[2] = 1, + .tx_sg_entries[3] = 1, + .tx_sg_entries[4] = 1, + .tx_sg_entries[5] = 1, + .tx_sg_entries[6] = 1, + .tx_sg_entries[7] = 1, .tx_fifo_sizes[0] = -1, .tx_fifo_sizes[1] = 1024, .tx_fifo_sizes[2] = -1, @@ -503,6 +540,14 @@ static void *hsi_modem_platform_data(void *data) .rx_dma_channels[5] = -1, .rx_dma_channels[6] = -1, .rx_dma_channels[7] = -1, + .rx_sg_entries[0] = 1, + .rx_sg_entries[1] = 1, + .rx_sg_entries[2] = 1, + .rx_sg_entries[3] = 1, + .rx_sg_entries[4] = 1, + .rx_sg_entries[5] = 1, + .rx_sg_entries[6] = 1, + .rx_sg_entries[7] = 1, .rx_fifo_sizes[0] = -1, .rx_fifo_sizes[1] = 1024, .rx_fifo_sizes[2] = -1, @@ -513,9 +558,61 @@ static void *hsi_modem_platform_data(void *data) .rx_fifo_sizes[7] = -1, }; - printk(KERN_INFO "HSI platform data setup\n"); +#elif defined(CONFIG_HSI_DLP) + static struct hsi_mid_platform_data mid_info = { + .tx_dma_channels[0] = -1, + .tx_dma_channels[1] = 0, + .tx_dma_channels[2] = 1, + .tx_dma_channels[3] = 2, + .tx_dma_channels[4] = 3, + .tx_dma_channels[5] = -1, + .tx_dma_channels[6] = -1, + .tx_dma_channels[7] = -1, + .tx_sg_entries[0] = 1, + .tx_sg_entries[1] = 1, + .tx_sg_entries[2] = 64, + .tx_sg_entries[3] = 64, + .tx_sg_entries[4] = 64, + .tx_sg_entries[5] = 1, + .tx_sg_entries[6] = 1, + .tx_sg_entries[7] = 1, + .tx_fifo_sizes[0] = 128, + .tx_fifo_sizes[1] = 128, + .tx_fifo_sizes[2] = 256, + .tx_fifo_sizes[3] = 256, + .tx_fifo_sizes[4] = 256, + .tx_fifo_sizes[5] = -1, + .tx_fifo_sizes[6] = -1, + .tx_fifo_sizes[7] = -1, + .rx_dma_channels[0] = -1, + .rx_dma_channels[1] = 4, + .rx_dma_channels[2] = 5, + .rx_dma_channels[3] = 6, + .rx_dma_channels[4] = 7, + .rx_dma_channels[5] = -1, + .rx_dma_channels[6] = -1, + .rx_dma_channels[7] = -1, + .rx_sg_entries[0] = 1, + .rx_sg_entries[1] = 1, + .rx_sg_entries[2] = 1, + .rx_sg_entries[3] = 1, + .rx_sg_entries[4] = 1, + .rx_sg_entries[5] = 1, + .rx_sg_entries[6] = 1, + .rx_sg_entries[7] = 1, + .rx_fifo_sizes[0] = 128, + .rx_fifo_sizes[1] = 128, + .rx_fifo_sizes[2] = 256, + .rx_fifo_sizes[3] = 256, + .rx_fifo_sizes[4] = 256, + .rx_fifo_sizes[5] = -1, + .rx_fifo_sizes[6] = -1, + .rx_fifo_sizes[7] = -1, + }; +#endif - printk(KERN_INFO "HSI mdm GPIOs %d, %d, %d, %d\n", + printk(KERN_INFO "HSI mdm GPIOs rst_out:%d," + " pwr_on:%d, rst_bbn:%d, fcdp_rb:%d\n", rst_out, pwr_on, rst_pmu, fcdp_rb); mid_info.gpio_mdm_rst_out = rst_out; diff --git a/drivers/hsi/clients/Kconfig b/drivers/hsi/clients/Kconfig index 95de9ed..dc36c87 100644 --- a/drivers/hsi/clients/Kconfig +++ b/drivers/hsi/clients/Kconfig @@ -100,3 +100,65 @@ config HSI_FFL_TTY_STATS endif +config HSI_DLP + tristate "Data Link Protocol protocol on HSI for LTE IMC modems" + default n + depends on HSI + help + If you say Y here, you will enable the DLP protocol for LTE IMC modem + over an HSI physical link. + This driver implements network interfaces and a TTY interface for + transferring data over HSI between an APE and an IMC modem. + + If unsure, say N. + +if HSI_DLP + +config HSI_DLP_TTY_NAME + string "Base name for the TTY" + default "IFX" + help + Sets the base name for the TTY associated to this IMC modem protocol + for LTE. + +config HSI_DLP_TTY_STATS + bool "Statistics to assess the performance of the protocol (TTY)" + default n + help + If you say Y here, you will instanciate performance related counters + for measuring the number of sent and received frames as well as their + total actual length in bytes. + + If not fine-tuning the HSI IMC LTE driver, say N. + +config HSI_DLP_NET_NAME + string "Base name for the network interfaces" + default "rmnet" + help + Sets the base name for the NETWORK interfaces associated to this IMC + modem protocol for LTE. + +config HSI_DLP_PDU_LENGTH + int "Fixed frame length" + default "4096" + range 4 131072 + help + Sets the fixed frame length in bytes to be used in this protocol + driver. This frame length must be a multiple of 4 bytes, set between + 4 bytes and 128 kiB (131072 bytes). + + Set to 4096 bytes by default. + +config HSI_DLP_HEADER_LENGTH + int "Fixed frame header length" + default "4" + range 0 4 + help + Sets the fixed frame header length in bytes to be used in this + protocol driver. This header length must be set to 4 in normal usage + or to 0 in raw protocol debug mode. + + Set to 4 bytes by default. + +endif + diff --git a/drivers/hsi/clients/Makefile b/drivers/hsi/clients/Makefile index 6919101..a800e26 100644 --- a/drivers/hsi/clients/Makefile +++ b/drivers/hsi/clients/Makefile @@ -3,9 +3,11 @@ # CFLAGS_hsi_ffl_tty.o := -DDEBUG +EXTRA_CFLAGS += -DDEBUG obj-$(CONFIG_SSI_PROTOCOL) += ssi_protocol.o obj-$(CONFIG_HSI_CHAR) += hsi_char.o obj-$(CONFIG_HSI_CMT_SPEECH) += cmt_speech.o obj-$(CONFIG_HSI_FFL_TTY) += hsi_ffl_tty.o - +obj-$(CONFIG_HSI_DLP) += hsi_dlp.o +hsi_dlp-objs := dlp_main.o dlp_ctrl.o dlp_tty.o dlp_net.o dlp_debug.o diff --git a/drivers/hsi/clients/dlp_ctrl.c b/drivers/hsi/clients/dlp_ctrl.c new file mode 100644 index 0000000..0ba4137 --- /dev/null +++ b/drivers/hsi/clients/dlp_ctrl.c @@ -0,0 +1,1255 @@ +/* + * dlp_ctrl.c + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI + * protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dlp_main.h" + +#define DEBUG_TAG 0x2 +#define DEBUG_VAR dlp_drv.debug + +/* + * 23 16 15 8 7 0 + * | | | | + * ................ ................ ................ + * + * PARAM3 PARAM2 PARAM1 + */ + +#define PARAM1(w) ((unsigned char) (w)) +#define PARAM2(w) ((unsigned char)((w) >> 8)) +#define PARAM3(w) ((unsigned char)((w) >> 16)) + +#define LSB(b) ((unsigned char) ((b) & 0xF)) +#define MSB(b) ((unsigned char) ((b) >> 4)) + +#define DLP_CMD_TX_TIMOUT 1000 /* 1 sec */ +#define DLP_CMD_RX_TIMOUT 5000 /* 5 sec */ + +#define DLP_ECHO_CMD_CHECKSUM 0xACAFFE + +#define DLP_CTRL_CMD_TO_STR(id) \ + (id == DLP_CMD_BREAK ? "BREAK": \ + (id == DLP_CMD_ECHO ? "ECHO": \ + (id == DLP_CMD_NOP) ? "NOP": \ + (id == DLP_CMD_CONF_CH) ? "CONF_CH": \ + (id == DLP_CMD_OPEN_CONN) ? "OPEN_CONN": \ + (id == DLP_CMD_CANCEL_CONN) ? "CANCEL_CONN": \ + (id == DLP_CMD_ACK) ? "ACK": \ + (id == DLP_CMD_NACK) ? "NACK": \ + (id == DLP_CMD_OPEN_CONN_OCTET) ? "OPEN_CONN_OCTET": \ + (id == DLP_CMD_CREDITS) ? "CREDITS" : "UNKNOWN")) + +#define DLP_CTRL_CTX dlp_drv.channels[DLP_CHANNEL_CTRL]->ch_data + +/* DLP commands list */ +#define DLP_CMD_BREAK 0x0 +#define DLP_CMD_ECHO 0x1 +#define DLP_CMD_NOP 0x4 +#define DLP_CMD_CONF_CH 0x5 +#define DLP_CMD_OPEN_CONN 0x7 +#define DLP_CMD_CANCEL_CONN 0xA +#define DLP_CMD_ACK 0xB +#define DLP_CMD_NACK 0xC +#define DLP_CMD_OPEN_CONN_OCTET 0xE +#define DLP_CMD_CREDITS 0xF + +/* Flow control */ +enum { + DLP_FLOW_CTRL_NONE, + DLP_FLOW_CTRL_CREDITS +}; + +/* Data format */ +enum { + DLP_DATA_FORMAT_RAW, + DLP_DATA_FORMAT_PACKET +}; + +/* Direction */ +enum { + DLP_DIR_TRANSMIT, + DLP_DIR_RECEIVE, + DLP_DIR_TRANSMIT_AND_RECEIVE +}; + +/** + * struct dlp_ctrl_cmd_params - DLP modem comamnd/response + * @data1: Command data (byte1) + * @data2: Command data (byte2) + * @data3: Command data (byte3) + * @channel: the HSI channel number + * @id: The command id + */ +struct dlp_command_params { + unsigned char data1:8; + unsigned char data2:8; + unsigned char data3:8; + + unsigned char channel:4; + unsigned char id:4; +}; + +/** + * struct dlp_ctrl_cmd - DLP comamnd + * @params: DLP modem comamnd/response + * @channel: the DLP channel context + * @status: Status of the transfer when completed + */ +struct dlp_command { + struct dlp_command_params params; + + struct dlp_channel *channel; + int status; +}; + +/* + * struct dlp_ctrl_context - CTRL channel private data + * + * @response: Modem response + * @readiness_wq: Modem readiness worqueue + * @tx_done: Wait for the command TX (command request) to be sent + * @rx_done: Wait for the command RX (command response) to be received + * @start_rx_cb: + * @stop_rx_cb: + */ +struct dlp_ctrl_context { + /* Modem readiness work & worqueue */ + struct workqueue_struct *readiness_wq; + struct work_struct readiness_work; + struct completion reset_done; + + /* Modem response */ + struct dlp_command response; + + /* Command RX/TX completion */ + struct completion tx_done; + struct completion rx_done; + + /* RX start/stop callbacks */ + hsi_client_cb start_rx_cb; + hsi_client_cb stop_rx_cb; + + /* GPIO */ + unsigned int gpio_mdm_rst_out; + unsigned int gpio_mdm_pwr_on; + unsigned int gpio_mdm_rst_bbn; + unsigned int gpio_fcdp_rb; +}; + +/* + * dlp_ctrl_coredump_it - Modem has signaled a core dump + * + */ +static irqreturn_t dlp_ctrl_coredump_it(int irq, void *data) +{ + int i; + struct dlp_channel *ch_ctx = data; + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + + PROLOG(); + + CRITICAL("Modem CORE_DUMP 0x%x", gpio_get_value(ctrl_ctx->gpio_fcdp_rb)); + + /* Call registered channels */ + for (i = 0; i < DLP_CHANNEL_COUNT; i++) { + ch_ctx = dlp_drv.channels[i]; + if (ch_ctx && ch_ctx->modem_coredump_cb) { + ch_ctx->modem_coredump_cb(ch_ctx); + } + } + + EPILOG(); + return IRQ_HANDLED; +} + +/* + * dlp_ctrl_reset_it - Modem has changed reset state + * + */ +static irqreturn_t dlp_ctrl_reset_it(int irq, void *data) +{ + int i, value; + struct dlp_channel *ch_ctx = data; + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + + PROLOG(); + + value = gpio_get_value(ctrl_ctx->gpio_mdm_rst_out); + + CRITICAL("Modem RESET_OUT 0x%x, reset_ignore: %d", + value, dlp_drv.reset_ignore); + if (dlp_drv.reset_ignore) { + /* Rising EDGE (Reset done) ? */ + if (value) + complete(&ctrl_ctx->reset_done); + + goto out; + } + + /* Unexpected reset received */ + dlp_drv.reset_ignore = 1; + + /* Call registered channels */ + for (i = 0; i < DLP_CHANNEL_COUNT; i++) { + ch_ctx = dlp_drv.channels[i]; + if (ch_ctx) { + /* Call any register callback */ + if (ch_ctx->modem_reset_cb) { + ch_ctx->modem_reset_cb(ch_ctx); + } + + /* Reset the credits value */ + ch_ctx->credits = 0; + } + } + +out: + EPILOG(); + return IRQ_HANDLED; +} + +/* + * + * + */ +static int dlp_ctrl_free_gpios(struct dlp_channel *ch_ctx, + struct device *dev) +{ + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + + PROLOG(); + + gpio_free(ctrl_ctx->gpio_fcdp_rb); + gpio_free(ctrl_ctx->gpio_mdm_rst_out); + gpio_free(ctrl_ctx->gpio_mdm_pwr_on); + gpio_free(ctrl_ctx->gpio_mdm_rst_bbn); + + if (dlp_drv.core_dump_irq) + free_irq(dlp_drv.core_dump_irq, dev); + + if (dlp_drv.reset_irq) + free_irq(dlp_drv.reset_irq, dev); + + EPILOG(); + return 0; +} + +static int dlp_ctrl_configure_gpios(struct dlp_channel *ch_ctx, + struct device *dev) +{ + int ret; + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + + PROLOG(); + + ret = gpio_request(ctrl_ctx->gpio_mdm_rst_bbn, "ifxHSIModem"); + ret += gpio_direction_output(ctrl_ctx->gpio_mdm_rst_bbn, 1); + ret += gpio_export(ctrl_ctx->gpio_mdm_rst_bbn, 1); + if (ret) { + CRITICAL("Unable to configure GPIO%d (RESET)", + ctrl_ctx->gpio_mdm_rst_bbn); + ret = -ENODEV; + goto free_ctx4; + } + pr_info("dlp: gpio rst_bbn %d\n", ctrl_ctx->gpio_mdm_rst_bbn); + + ret = gpio_request(ctrl_ctx->gpio_mdm_pwr_on, "ifxHSIModem"); + ret += gpio_direction_output(ctrl_ctx->gpio_mdm_pwr_on, 1); + ret += gpio_export(ctrl_ctx->gpio_mdm_pwr_on, 1); + if (ret) { + CRITICAL("Unable to configure GPIO%d (ON)", + ctrl_ctx->gpio_mdm_pwr_on); + ret = -ENODEV; + goto free_ctx3; + } + + pr_info("dlp: gpio pwr_on %d\n", ctrl_ctx->gpio_mdm_pwr_on); + + /* set up irq for modem reset line */ + ret = gpio_request(ctrl_ctx->gpio_mdm_rst_out, "ifxHSIModem"); + ret += gpio_direction_input(ctrl_ctx->gpio_mdm_rst_out); + ret += gpio_export(ctrl_ctx->gpio_mdm_rst_out, 0); + if (ret) { + CRITICAL("Unable to configure GPIO%d (RST_OUT)", + ctrl_ctx->gpio_mdm_rst_out); + ret = -ENODEV; + goto free_ctx2; + } + + dlp_drv.reset_irq = gpio_to_irq(ctrl_ctx->gpio_mdm_rst_out); + if (dlp_drv.reset_irq < 0) { + ret = -ENODEV; + goto free_ctx2; + } + + pr_info("dlp: gpio rst_out %d\n", ctrl_ctx->gpio_mdm_rst_out); + + ret = request_irq(dlp_drv.reset_irq, + dlp_ctrl_reset_it, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DRVNAME, + ch_ctx); + if (ret) { + CRITICAL("IRQ request failed for GPIO%d (RST_OUT)", + dlp_drv.reset_irq); + ret = -ENODEV; + goto free_ctx2; + } + + /* set up input for core dump interrupt line */ + ret = gpio_request(ctrl_ctx->gpio_fcdp_rb, "ifxHSIModem"); + ret += gpio_direction_input(ctrl_ctx->gpio_fcdp_rb); + ret += gpio_export(ctrl_ctx->gpio_fcdp_rb, 0); + if (ret) { + CRITICAL("Unable to configure GPIO%d (CORE DUMP)", + ctrl_ctx->gpio_fcdp_rb); + ret = -ENODEV; + goto free_all; + } + + dlp_drv.core_dump_irq = gpio_to_irq(ctrl_ctx->gpio_fcdp_rb); + if (dlp_drv.core_dump_irq < 0) { + ret = -ENODEV; + goto free_all; + } + + ret = request_irq(dlp_drv.core_dump_irq, + dlp_ctrl_coredump_it, + IRQF_TRIGGER_RISING, DRVNAME, + ch_ctx); + if (ret) { + CRITICAL("IRQ request failed for GPIO%d (CORE DUMP)", + ctrl_ctx->gpio_fcdp_rb); + ret = -ENODEV; + goto free_all; + } + + pr_info("dlp: gpio fcdp_rb %d\n", ctrl_ctx->gpio_fcdp_rb); + EPILOG(); + return ret; + +free_all: + dlp_ctrl_free_gpios(ch_ctx, dev); + return ret; + +free_ctx2: + gpio_free(ctrl_ctx->gpio_mdm_rst_out); +free_ctx3: + gpio_free(ctrl_ctx->gpio_mdm_pwr_on); +free_ctx4: + gpio_free(ctrl_ctx->gpio_mdm_rst_bbn); + + if (dlp_drv.core_dump_irq) + free_irq(dlp_drv.core_dump_irq, dev); + + if (dlp_drv.reset_irq) + free_irq(dlp_drv.reset_irq, dev); + + EPILOG(); + return ret; +} + +/**************************************************************************** + * + * Control flow + * + * + ***************************************************************************/ + +/* + * + */ +static void dlp_ctrl_save_rx_callbacks(struct dlp_ctrl_context *ctrl_ctx) +{ + ctrl_ctx->start_rx_cb = dlp_drv.client->hsi_start_rx; + ctrl_ctx->stop_rx_cb = dlp_drv.client->hsi_stop_rx; + + dlp_drv.client->hsi_start_rx = NULL; + dlp_drv.client->hsi_stop_rx = NULL; +} + +/* + * + */ +static void dlp_ctrl_restore_rx_callbacks(struct dlp_ctrl_context *ctrl_ctx) +{ + dlp_drv.client->hsi_start_rx = ctrl_ctx->start_rx_cb; + dlp_drv.client->hsi_stop_rx = ctrl_ctx->stop_rx_cb; + + ctrl_ctx->start_rx_cb = NULL; + ctrl_ctx->stop_rx_cb = NULL; +} + +/* + * + */ +static struct dlp_command *dlp_ctrl_cmd_alloc(struct dlp_channel *ch_ctx, + unsigned char id, + unsigned char param1, + unsigned char param2, + unsigned char param3) +{ + int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL; + struct dlp_command *dlp_cmd; + + /* Allocate DLP command */ + dlp_cmd = kmalloc(sizeof(struct dlp_command), flags); + if (!dlp_cmd) { + CRITICAL("Out of memory (dlp_cmd)"); + goto out; + } + + /* Set command params */ + dlp_cmd->params.id = id; + dlp_cmd->params.channel = ch_ctx->hsi_channel; + dlp_cmd->params.data1 = param1; + dlp_cmd->params.data2 = param2; + dlp_cmd->params.data3 = param3; + + dlp_cmd->channel = ch_ctx; + +out: + return dlp_cmd; +} + +/* + * + */ +static inline void dlp_ctrl_cmd_free(struct dlp_command *dlp_cmd) +{ + if (dlp_cmd) + kfree(dlp_cmd); +} + +/* + * + */ +static void dlp_ctrl_msg_destruct(struct hsi_msg *msg) +{ + struct dlp_command *dlp_cmd = msg->context; + + PROLOG("hsi_ch:%d, cmd:%s, msg:0x%p", + dlp_cmd->params.channel, + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), msg); + + /* Delete the received msg */ + dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE); + + /* Delete the command */ + kfree(dlp_cmd); + + EPILOG(); +} + +/** + * Synchronous TX message callback + * + * @msg: a reference to the HSI msg + * + * This function: + * - Set the xfer status + * - wakeup the caller + * - delete the hsi message + */ +static void dlp_ctrl_complete_tx(struct hsi_msg *msg) +{ + struct dlp_command *dlp_cmd = msg->context; + struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX; + + PROLOG("hsi_ch:%d, cmd:%s, msg:0x%p", + dlp_cmd->params.channel, + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), msg); + + dlp_cmd->status = (msg->status == HSI_STATUS_COMPLETED) ? 0 : -EIO; + + /* Command done, notify the sender */ + complete(&ctrl_ctx->tx_done); + + /* Delete the received msg */ + dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE); + + EPILOG(); +} + +/** + * Asynchronous TX message callback + * + * @msg: a reference to the HSI msg + * + * This function: + * - delete the hsi message + */ +static void dlp_ctrl_complete_tx_async(struct hsi_msg *msg) +{ + struct dlp_command *dlp_cmd = msg->context; + + PROLOG("cmd:%s, hsi_ch:%d, msg:0x%p", + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), + dlp_cmd->params.channel, msg); + + /* Delete the received msg */ + dlp_pdu_free(msg, DLP_CTRL_PDU_SIZE); + + /* Delete the command */ + kfree(dlp_cmd); + + EPILOG(); +} + +/* + * + */ +static void dlp_ctrl_complete_rx(struct hsi_msg *msg) +{ + struct dlp_channel *ch_ctx; + struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX; + struct dlp_command_params params; + int hsi_channel, ret, response, msg_complete; + + /* Copy the reponse */ + memcpy(¶ms, + sg_virt(msg->sgt.sgl), sizeof(struct dlp_command_params)); + + response = -1; + msg_complete = (msg->status == HSI_STATUS_COMPLETED); + + PROLOG("cmd:%s, hsi_ch:%d, params: 0x%02X%02X%02X, msg_status: %d", + DLP_CTRL_CMD_TO_STR(params.id), + params.channel, + params.data1, params.data2, params.data3, msg->status); + + hsi_channel = params.channel; + if ((hsi_channel < 0) || (hsi_channel >= DLP_CHANNEL_COUNT)) { + CRITICAL("CREDITS: Invalid channel id (%d)", hsi_channel); + BUG_ON(1); /* FIXME: We have BIGGGGGG pb (To be removed ?) */ + goto out; + } + + ch_ctx = dlp_drv.channels[hsi_channel]; + + switch (params.id) { + case DLP_CMD_CREDITS: + if (msg_complete) { + unsigned long flags; + + /* 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); + } + + PTRACE_NO_FUNC("New CREDITS value: %d (+%d)\n", ret, + params.data3); + response = DLP_CMD_ACK; + } + break; + + case DLP_CMD_OPEN_CONN: + if (msg_complete) { + ret = ((params.data2 << 8) | params.data1); + response = DLP_CMD_ACK; + + /* Check the requested PDU size */ + if (ch_ctx->pdu_size != ret) { + CRITICAL + ("Unexpected PDU size: %d => Expected: %d (ch: %d)", + ret, ch_ctx->pdu_size, + ch_ctx->hsi_channel); + + response = DLP_CMD_NACK; + } + } + 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(&ctrl_ctx->rx_done); + break; + } + + /* Send command response */ + if (response != -1) { + struct dlp_command *dlp_cmd; + struct hsi_msg *tx_msg = NULL; + + /* Allocate the DLP command */ + dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, + response, + params.data1, + params.data2, params.data3); + if (!dlp_cmd) { + CRITICAL("Out of memory (dlp_cmd)"); + goto push_rx; + } + + /* Allocate a new TX msg */ + tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL, + HSI_MSG_WRITE, + DLP_CTRL_PDU_SIZE, + 1, + dlp_cmd, + dlp_ctrl_complete_tx_async, + dlp_ctrl_msg_destruct); + + if (!tx_msg) { + CRITICAL("dlp_pdu_alloc(TX) failed"); + + /* Delete the command */ + kfree(dlp_cmd); + + goto push_rx; + } + + /* 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) { + CRITICAL("hsi_async(TX) failed ! (%s, ret:%d)", + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), ret); + + /* Free the TX msg */ + dlp_pdu_free(tx_msg, DLP_CTRL_PDU_SIZE); + + /* Delete the command */ + kfree(dlp_cmd); + } + } + +push_rx: + /* Push the RX msg again for futur response */ + ret = hsi_async(msg->cl, msg); + if (ret) { + CRITICAL("hsi_async() failed, ret:%d", 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, DLP_CTRL_PDU_SIZE); + } + +out: + EPILOG(); +} + +/** + * 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 + * @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 param1, + unsigned char param2, unsigned char param3) +{ + int ret = 0; + struct dlp_ctrl_context *ctrl_ctx = DLP_CTRL_CTX; + struct dlp_command *dlp_cmd; + struct hsi_msg *tx_msg = NULL; + + PROLOG("cmd:%s, hsi_ch:%d", + DLP_CTRL_CMD_TO_STR(id), ch_ctx->hsi_channel); + + /* Backup RX callback */ + dlp_ctrl_save_rx_callbacks(ctrl_ctx); + + /* Allocate the DLP command */ + dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, id, param1, param2, param3); + if (!dlp_cmd) { + CRITICAL("Out of memory (dlp_cmd)"); + ret = -ENOMEM; + goto out; + } + + /* Allocate a new TX msg */ + tx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL, + HSI_MSG_WRITE, + DLP_CTRL_PDU_SIZE, + 1, + dlp_cmd, + dlp_ctrl_complete_tx, dlp_ctrl_msg_destruct); + + if (!tx_msg) { + CRITICAL("dlp_pdu_alloc(TX) failed"); + 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) { + CRITICAL("hsi_async(TX) failed ! (%s, ret:%d)", + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), ret); + + goto free_tx; + } + + /* Wait for TX msg to be sent */ + ret = wait_for_completion_timeout(&ctrl_ctx->tx_done, + msecs_to_jiffies(DLP_CMD_TX_TIMOUT)); + if (ret == 0) { + CRITICAL("TX Timeout => %s, hsi_ch: %d", + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), + dlp_cmd->params.channel); + + ret = -EIO; + goto out; + } + + /* TX msg sent, check the status */ + if (dlp_cmd->status) { + CRITICAL("Failed to send %s", + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id)); + + ret = -EIO; + goto out; + } + + /* TX OK, Wait for the response */ + ret = wait_for_completion_timeout(&ctrl_ctx->rx_done, + msecs_to_jiffies(DLP_CMD_RX_TIMOUT)); + if (ret == 0) { + CRITICAL("RX Timeout => %s, hsi_ch: %d", + DLP_CTRL_CMD_TO_STR(dlp_cmd->params.id), + dlp_cmd->params.channel); + + ret = -EIO; + goto out; + } + + /* Check the response */ + ret = 0; + if ((ctrl_ctx->response.params.id != response_id) || + (ctrl_ctx->response.params.data1 != param1) || + (ctrl_ctx->response.params.data2 != param2) || + (ctrl_ctx->response.params.data3 != param3)) { + + CRITICAL("Unexpected response received: 0x%x%x [%02X%02X%02X]" + " => expected: 0x%x%x [%02X%02X%02X]", + ctrl_ctx->response.params.id, + ctrl_ctx->response.params.channel, + ctrl_ctx->response.params.data1, + ctrl_ctx->response.params.data2, + ctrl_ctx->response.params.data3, + response_id, + ch_ctx->hsi_channel, param1, param2, param3); + + ret = -EIO; + } + + /* Restore RX callback */ + dlp_ctrl_restore_rx_callbacks(ctrl_ctx); + + /* Everything is OK */ + EPILOG("%d", ret); + return ret; + +free_tx: + /* Free the TX msg */ + dlp_pdu_free(tx_msg, DLP_CTRL_PDU_SIZE); + +free_cmd: + /* Free the DLP command */ + dlp_ctrl_cmd_free(dlp_cmd); + +out: + /* Restore RX callback */ + dlp_ctrl_restore_rx_callbacks(ctrl_ctx); + + EPILOG("%d", ret); + return ret; +} + +/** + * 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; + + PROLOG(); + + /* Allocate the DLP command */ + dlp_cmd = dlp_ctrl_cmd_alloc(ch_ctx, DLP_CMD_NOP, 0, 0, 0); + if (!dlp_cmd) { + CRITICAL("Out of memory (dlp_cmd)"); + ret = -ENOMEM; + goto out; + } + + /* Allocate a new RX msg */ + rx_msg = dlp_pdu_alloc(DLP_CHANNEL_CTRL, + HSI_MSG_READ, + DLP_CTRL_PDU_SIZE, + 1, + dlp_cmd, + dlp_ctrl_complete_rx, dlp_ctrl_msg_destruct); + + if (!rx_msg) { + CRITICAL("dlp_pdu_alloc() failed"); + ret = -ENOMEM; + goto free_cmd; + } + + /* 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, DLP_CTRL_PDU_SIZE); + +free_cmd: + /* Delete the command */ + kfree(dlp_cmd); + +out: + EPILOG(); + return ret; +} + +static inline void dlp_ctrl_set_modem_readiness(unsigned int value) +{ + unsigned long flags; + + spin_lock_irqsave(&dlp_drv.lock, flags); + dlp_drv.modem_ready = value; + spin_unlock_irqrestore(&dlp_drv.lock, flags); +} + + +/* +* @brief +* +* @param work +*/ +static void dlp_ctrl_ipc_readiness(struct work_struct *work) +{ + struct dlp_channel *ch_ctx = dlp_drv.channels[DLP_CHANNEL_CTRL]; + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + int ret; + + PROLOG(); + + /* Set the modem readiness state */ + dlp_ctrl_set_modem_readiness(0); + + /* Wait for RESET_OUT */ + wait_for_completion(&ctrl_ctx->reset_done); + + /* Send ECHO continuously until the modem become ready */ + do { + ret = 0; + // ret = dlp_ctrl_send_echo_cmd(ctrl_ctx); + if (ret == 0) { + /* Set the modem state */ + dlp_ctrl_set_modem_readiness(1); + } + } + while (ret); + + EPILOG(); +} + + +/**************************************************************************** + * + * Exported functions + * + ***************************************************************************/ + +/** + * dlp_ctrl_modem_reset - activity required to bring up modem + * @ch_ctx: a reference to related channel context + * + * Toggle gpios required to bring up modem power and start modem. + * This can be called after the modem has been started to reset it. + */ +int dlp_ctrl_modem_reset(struct dlp_channel *ch_ctx) +{ + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + int ret = 0; + + PROLOG(); + + /* AP requested reset => just ignore */ + dlp_drv.reset_ignore = 1; + + gpio_set_value(ctrl_ctx->gpio_mdm_rst_bbn, 0); + mdelay(DLP_POWER_ON_INTERLINE_DELAY); + + gpio_set_value(ctrl_ctx->gpio_mdm_rst_bbn, 1); + msleep(DLP_POWER_ON_POST_DELAY); + + EPILOG(); + return ret; +} + +/* +* @brief +* +* @param ch_ctx +* +* @return +*/ +static int dlp_ctrl_modem_power(struct dlp_channel *ch_ctx) +{ + int ret = 0; + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + + PROLOG(); + + /* AP requested reset => just ignore */ + dlp_drv.reset_ignore = 1; + + gpio_set_value(ctrl_ctx->gpio_mdm_pwr_on, 1); + msleep(DLP_POWER_ON_INTERLINE_DELAY); + + gpio_set_value(ctrl_ctx->gpio_mdm_pwr_on, 0); + msleep(DLP_POWER_ON_POST_DELAY); + + EPILOG(); + return ret; +} + + +/* +* @brief +* +* @return +*/ +inline unsigned int dlp_ctrl_modem_is_ready(void) +{ + unsigned long flags; + unsigned int value; + + spin_lock_irqsave(&dlp_drv.lock, flags); + value = dlp_drv.modem_ready; + spin_unlock_irqrestore(&dlp_drv.lock, flags); + + return value; +} + +/* +* @brief +* +* @param index +* @param dev +* +* @return +*/ +struct dlp_channel *dlp_ctrl_ctx_create(unsigned int index, struct device *dev) +{ + int ret, i; + struct hsi_client *client = to_hsi_client(dev); + struct hsi_mid_platform_data *pd = client->device.platform_data; + struct dlp_channel *ch_ctx; + struct dlp_ctrl_context *ctrl_ctx; + + PROLOG(); + + ch_ctx = kzalloc(sizeof(struct dlp_channel), GFP_KERNEL); + if (!ch_ctx) { + CRITICAL("Unable to allocate memory (ch_ctx)"); + goto out; + } + + /* Allocate the context private data */ + ctrl_ctx = kzalloc(sizeof(struct dlp_ctrl_context), GFP_KERNEL); + if (!ctrl_ctx) { + CRITICAL("Unable to allocate memory (ctrl_ctx)"); + goto free_ch; + } + + /* Create a workqueue to check the modem readiness */ + ctrl_ctx->readiness_wq = create_singlethread_workqueue(DRVNAME "-mdmreadiness"); + if (!ctrl_ctx->readiness_wq) { + CRITICAL("Unable to create modem readiness workqueue"); + goto free_ctx; + } + + /* Save params */ + ch_ctx->ch_data = ctrl_ctx; + ch_ctx->hsi_channel = index; + ch_ctx->pdu_size = DLP_CTRL_PDU_SIZE; + ch_ctx->rx.config = client->rx_cfg; + ch_ctx->tx.config = client->tx_cfg; + + spin_lock_init(&ch_ctx->lock); + init_completion(&ctrl_ctx->reset_done); + INIT_WORK(&ctrl_ctx->readiness_work, dlp_ctrl_ipc_readiness); + + /* Configure GPIOs */ + ctrl_ctx->gpio_mdm_rst_out = pd->gpio_mdm_rst_out; + ctrl_ctx->gpio_mdm_pwr_on = pd->gpio_mdm_pwr_on; + ctrl_ctx->gpio_mdm_rst_bbn = pd->gpio_mdm_rst_bbn; + ctrl_ctx->gpio_fcdp_rb = pd->gpio_fcdp_rb; + + ret = dlp_ctrl_configure_gpios(ch_ctx, dev); + if (ret) { + goto free_ctx; + } + + /* Power on the modem */ + ret = dlp_ctrl_modem_power(ch_ctx); + if (ret) { + ret = -ENODEV; + dlp_ctrl_free_gpios(ch_ctx, dev); + goto free_ctx; + } + + /* Reset the modem */ + ret = dlp_ctrl_modem_reset(ch_ctx); + if (ret) { + ret = -ENODEV; + dlp_ctrl_free_gpios(ch_ctx, dev); + goto free_ctx; + } + + /* Init the RX/TX completion */ + init_completion(&ctrl_ctx->rx_done); + init_completion(&ctrl_ctx->tx_done); + + /* Set ch_ctx, not yet done in the probe */ + dlp_drv.channels[DLP_CHANNEL_CTRL] = ch_ctx; + + /* Push RX pdus for CREDTIS/OPEN_CONN commands */ + for (i = 0; i < DLP_CHANNEL_COUNT; i++) + dlp_ctrl_push_rx_pdu(ch_ctx); + + /* Start the modem readiness worqeue */ + queue_work(ctrl_ctx->readiness_wq, &ctrl_ctx->readiness_work); + + EPILOG(); + return ch_ctx; + +free_ctx: + kfree(ctrl_ctx); + +free_ch: + kfree(ch_ctx); + +out: + EPILOG("Failed"); + return NULL; +} + +/* +* @brief +* +* @param ch_ctx +* +* @return +*/ +int dlp_ctrl_ctx_delete(struct dlp_channel *ch_ctx) +{ + struct dlp_ctrl_context *ctrl_ctx = ch_ctx->ch_data; + int ret = 0; + + PROLOG(); + + /* Delete the modem readiness worqueue */ + destroy_workqueue(ctrl_ctx->readiness_wq); + + /* Free GPIOs */ + + /* Free the CTRL context */ + kfree(ctrl_ctx); + + /* Free the ch_ctx */ + kfree(ch_ctx); + + EPILOG(); + return ret; +} + +/* + * + */ +int dlp_ctrl_send_echo_cmd(struct dlp_channel *ch_ctx) +{ + int ret = 0; + unsigned char param1 = PARAM1(DLP_ECHO_CMD_CHECKSUM); + unsigned char param2 = PARAM2(DLP_ECHO_CMD_CHECKSUM); + unsigned char param3 = PARAM3(DLP_ECHO_CMD_CHECKSUM); + + PROLOG(); + + /* Send the command */ + ret = dlp_ctrl_cmd_send(ch_ctx, + DLP_CMD_ECHO, + DLP_CMD_ECHO, param1, param2, param3); + EPILOG(); + return ret; +} + +/* + * + */ +int dlp_ctrl_send_open_conn_cmd(struct dlp_channel *ch_ctx) +{ + int ret = 0; + unsigned char param1 = PARAM1(ch_ctx->pdu_size); + unsigned char param2 = PARAM2(ch_ctx->pdu_size); + + PROLOG(); + + /* Send the command */ + ret = dlp_ctrl_cmd_send(ch_ctx, + DLP_CMD_OPEN_CONN, + DLP_CMD_ACK, param1, param2, 0); + + EPILOG(); + return ret; +} + +/* + * + */ +int dlp_ctrl_send_cancel_conn_cmd(struct dlp_channel *ch_ctx) +{ + int ret = 0; + // FIXME: both or just xmit/receive ? + //unsigned char param3 = PARAM1(DLP_DIR_TRANSMIT_AND_RECEIVE); + unsigned char param3; + + PROLOG(); + + // FIXME: TX & RX dont work ==> the modem response is TX only + param3 = PARAM1(DLP_DIR_TRANSMIT); + + /* Send the command */ + ret = dlp_ctrl_cmd_send(ch_ctx, + DLP_CMD_CANCEL_CONN, DLP_CMD_ACK, 0, 0, param3); + EPILOG(); + return ret; +} + + +/* + * Modem reset sysfs entries + */ +static int set_modem_reset(const char *val, struct kernel_param *kp) +{ + unsigned long reset_request; + + if (strict_strtoul(val, 16, &reset_request) < 0) + return -EINVAL; + + if (reset_request) { + struct dlp_channel *ch_ctx = dlp_drv.channels[DLP_CHANNEL_CTRL]; + + dlp_ctrl_modem_reset(ch_ctx); + } + + return 0; +} + +static int get_modem_reset(const char *val, struct kernel_param *kp) +{ + unsigned long reset_ongoing = 0; + + return sprintf(val, "%d", reset_ongoing); +} + +/* + * Modem hangup sysfs entries + */ +static int set_hangup_reasons(const char *val, struct kernel_param *kp) +{ + unsigned long reset_request; + + if (strict_strtoul(val, 16, &reset_request) < 0) + return -EINVAL; + + return 0; +} + +static int get_hangup_reasons(const char *val, struct kernel_param *kp) +{ + unsigned long hangup_reasons = 0; + + return sprintf(val, "%d", hangup_reasons); +} + + +module_param_call(reset_modem, set_modem_reset, get_modem_reset, NULL, 0644); +module_param_call(hangup_reasons, set_hangup_reasons, get_hangup_reasons, + NULL, 0644); + diff --git a/drivers/hsi/clients/dlp_debug.c b/drivers/hsi/clients/dlp_debug.c new file mode 100644 index 0000000..bc2f7ba --- /dev/null +++ b/drivers/hsi/clients/dlp_debug.c @@ -0,0 +1,215 @@ +/* + * dlp_debug.c + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI + * protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include + +#include "../dlp_debug.h" +#include "dlp_main.h" + +#define DEBUG_TAG 0x1 +#define DEBUG_VAR dlp_drv.debug + +/* + * + * + */ +void dlp_dbg_dump_data_as_byte(unsigned char *data, + int nb_bytes, + int bytes_per_line, int should_dump, int is_rx) +{ + int i; + + if (should_dump) + printk("-----\n"); + else if (is_rx) + PRINT_RX("-----\n"); + else + PRINT_TX("-----\n"); + + for (i = 0; i < nb_bytes; i++) { + if (should_dump) + printk("%02X ", data[i]); + else if (is_rx) + PRINT_RX("%02X ", data[i]); + else + PRINT_TX("%02X ", data[i]); + + /* Print requested items/line */ + if (((i + 1) & (bytes_per_line - 1)) == 0) { + if (should_dump) + printk("\n"); + else if (is_rx) + PRINT_RX("\n"); + else + PRINT_TX("\n"); + } + } + + /* Previous line was less than "bytes_per_line" */ + if ((i & (bytes_per_line - 1)) != 0) { + if (should_dump) + printk("\n"); + else if (is_rx) + PRINT_RX("\n"); + else + PRINT_TX("\n"); + } + + if (should_dump) + printk("-----\n"); + else if (is_rx) + PRINT_RX("-----\n"); + else + PRINT_TX("-----\n"); +} + +void dlp_dbg_dump_data_as_word(unsigned int *data, + int nb_words, + int words_per_line, int should_dump, int is_rx) +{ + int i; + + if (should_dump) + printk("-----\n"); + else if (is_rx) + PRINT_RX("-----\n"); + else + PRINT_TX("-----\n"); + + for (i = 0; i < nb_words; i++) { + if (should_dump) + printk("%08X ", data[i]); + else if (is_rx) + PRINT_RX("%08X ", data[i]); + else + PRINT_TX("%08X ", data[i]); + + /* Print requested items/line */ + if (((i + 1) & (words_per_line - 1)) == 0) { + if (should_dump) + printk("\n"); + else if (is_rx) + PRINT_RX("\n"); + else + PRINT_TX("\n"); + } + } + + /* Previous line was less than "words_per_line" */ + if ((i & (words_per_line - 1)) != 0) { + if (should_dump) + printk("\n"); + else if (is_rx) + PRINT_RX("\n"); + else + PRINT_TX("\n"); + } + + if (should_dump) + printk("-----\n"); + else if (is_rx) + PRINT_RX("-----\n"); + else + PRINT_TX("-----\n"); +} + +/* +* @brief +* +* @param pdu : The pdu to dump +* @param items_per_line : Nb of items (bytes/words) to display/line +* @param items_to_dump : Nb of items (bytes/words) to dump +* @param dump_as_words : Dump data as sequence of WORDs instead of BYEs +*/ +void dlp_dbg_dump_pdu(struct hsi_msg *pdu, + int items_per_line, int items_to_dump, int dump_as_words) +{ + int i; + struct scatterlist *sg = pdu->sgt.sgl; + unsigned char *data; + int len, dumped, is_corrupted, is_rx; + + PROLOG("0x%p [nents: %d, hsi_ch: %d, status: %d, " + "actual_len: %d, break_frame: %d", + pdu, + pdu->sgt.nents, + pdu->channel, pdu->status, pdu->actual_len, pdu->break_frame); + + dumped = 0; + is_corrupted = 0; + is_rx = (pdu->ttype == HSI_MSG_READ); + + if (is_rx) + PRINT_RX("RX PDU (0x%p): \n", pdu); + else + PRINT_TX("TX PDU (0x%p): \n", pdu); + + /* Check the PDU signature */ + data = sg_virt(sg); + if (!dlp_pdu_header_valid(pdu)) { + is_corrupted = 1; + } + + for (i = 0; i < pdu->sgt.nents; i++) { + data = sg_virt(sg); + len = sg->length; + + /* All requested items are dumped ? */ + if (len < items_to_dump) { + if (dumped >= items_to_dump) { + break; + } else { + if ((len + dumped) > items_to_dump) { + len = items_to_dump - dumped; + } + } + } else { + len = items_to_dump - dumped; + } + + /* Dump current SGL entry */ + if (dump_as_words) + dlp_dbg_dump_data_as_word((u32 *) data, + len, + items_per_line, + is_corrupted, is_rx); + else + dlp_dbg_dump_data_as_byte(data, + len, + items_per_line, + is_corrupted, is_rx); + + /* Move to the next SGL entry */ + sg = sg_next(sg); + dumped += len; + } + + EPILOG(); +} diff --git a/drivers/hsi/clients/dlp_main.c b/drivers/hsi/clients/dlp_main.c new file mode 100644 index 0000000..deff70d --- /dev/null +++ b/drivers/hsi/clients/dlp_main.c @@ -0,0 +1,1817 @@ +/* + * dlp_main.c + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (DLP)). This driver is implementing a 5-channel HSI + * protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dlp_main.h" + +#define DEBUG_TAG 0x1 +#define DEBUG_VAR dlp_drv.debug + +/* Forward declarations */ +static void dlp_pdu_destructor(struct hsi_msg *pdu); + +static inline void dlp_ctx_update_state(struct dlp_xfer_ctx *xfer_ctx); + +static inline void dlp_fifo_recycled_push(struct dlp_xfer_ctx *xfer_ctx, + struct hsi_msg *pdu); + +/* + * Static protocol driver global variables + */ +struct dlp_driver dlp_drv; + +/* Module debug parameter */ +module_param_named(debug, dlp_drv.debug, int, S_IRUGO | S_IWUSR); + +/* + * + * + */ +static int dlp_proc_show(struct seq_file *m, void *v) +{ + int i; + unsigned long flags; + struct dlp_channel *ch_ctx; + + for (i = 0; i < DLP_CHANNEL_COUNT; i++) { + ch_ctx = dlp_drv.channels[i]; + + seq_printf(m, "\nChannel: %d\n", ch_ctx->hsi_channel); + seq_printf(m, "-------------\n"); + seq_printf(m, " pdu_size : %d\n", ch_ctx->pdu_size); + seq_printf(m, " credits : %d\n", ch_ctx->credits); + + read_lock_irqsave(&ch_ctx->rx.lock, flags); + seq_printf(m, " RX ctx:\n"); + seq_printf(m, " wait_max: %d\n", ch_ctx->rx.wait_max); + seq_printf(m, " ctrl_max: %d\n", ch_ctx->rx.ctrl_max); + seq_printf(m, " all_len : %d\n", ch_ctx->rx.all_len); + seq_printf(m, " ctrl_len: %d\n", ch_ctx->rx.ctrl_len); + seq_printf(m, " wait_len: %d\n", ch_ctx->rx.wait_len); + seq_printf(m, " seq_num : %d\n", ch_ctx->rx.seq_num); + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + + read_lock_irqsave(&ch_ctx->tx.lock, flags); + seq_printf(m, " TX ctx:\n"); + seq_printf(m, " wait_max: %d\n", ch_ctx->tx.wait_max); + seq_printf(m, " ctrl_max: %d\n", ch_ctx->tx.ctrl_max); + seq_printf(m, " all_len : %d\n", ch_ctx->tx.all_len); + seq_printf(m, " ctrl_len: %d\n", ch_ctx->tx.ctrl_len); + seq_printf(m, " wait_len: %d\n", ch_ctx->tx.wait_len); + seq_printf(m, " seq_num : %d\n", ch_ctx->tx.seq_num); + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + } + + return 0; +} + +static int dlp_proc_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, dlp_proc_show, PDE(inode)->data); +} + +static const struct file_operations dlp_proc_ops = { + .open = dlp_proc_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release +}; + +/** + * from_usecs - translating usecs to jiffies + * @delay: the dealy in usecs + * + * Returns the delay rounded up to the next jiffy and prevent it to be set + * to zero, as all delayed function calls shall occur to the next jiffy (at + * least). + */ +inline unsigned long from_usecs(const unsigned long delay) +{ + unsigned long j = usecs_to_jiffies(delay); + + if (j == 0) + j = 1; + + return j; +} + +/**************************************************************************** + * + * PDU creation and deletion + * + ***************************************************************************/ + +/* + * + * + */ +void dlp_pdu_dump(struct hsi_msg *pdu, int as_string) +{ + u32 *addr = sg_virt(pdu->sgt.sgl); + + PROLOG("0x%p [ttype: %s, nents: %d, status: %d, " + "channel: %d, actual_len: %d, break_frame: %d]", + pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX", pdu->sgt.nents, + pdu->status, pdu->channel, pdu->actual_len, pdu->break_frame); + + /* Display as HEX */ + if (pdu->sgt.sgl->length == DLP_CTRL_PDU_SIZE) { + PDEBUG("\n\t data[1-4] : %08X", *(addr)); + } else { + PDEBUG("\n\t data[1-4] : %08X %08X %08X %08X\n" + "\t data[5-8] : %08X %08X %08X %08X\n" + "\t data[9-12] : %08X %08X %08X %08X\n" + "\t data[13-16] : %08X %08X %08X %08X\n", + *(addr), *(addr + 1), *(addr + 2), *(addr + 3), + *(addr + 4), *(addr + 5), *(addr + 6), *(addr + 7), + *(addr + 8), *(addr + 9), *(addr + 10), *(addr + 11), + *(addr + 12), *(addr + 13), *(addr + 14), *(addr + 15)); + + /* Display as STRING */ + if (as_string) { + unsigned int len; + unsigned char *str; + + len = *(addr + 2); + addr += 4; + str = (char *)addr; + str[len] = '\0'; + + PDEBUG("\n\t len: %d, data: \"%s\"", len, str); + } + } + + EPILOG(); +} + +/** + * dlp_buffer_alloc - helper function to allocate memory + * @buff_size: buffer size + * @dma_addr: buffer dma address + * + * Returns + * - a reference to the newly allocated buffer + * - NULL if an error occured. + */ +void *dlp_buffer_alloc(unsigned int buff_size, dma_addr_t * dma_addr) +{ + void *buff; + int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL; + + if (dlp_drv.is_dma_capable) { + buff = dma_alloc_coherent(dlp_drv.controller, + buff_size, dma_addr, flags); + } else { + if ((buff_size >= PAGE_SIZE) + && ((buff_size & (PAGE_SIZE - 1)) == 0)) { + buff = + (void *)__get_free_pages(flags, + get_order(buff_size)); + } else { + buff = kmalloc(buff_size, flags); + } + } + + return buff; +} + +/** + * dlp_buffer_free - Free memory alloccted by dlp_buffer_alloc + * @buff: buffer address + * @dma_addr: buffer dma address + * @buff_size: buffer size + * + */ +void dlp_buffer_free(void *buff, dma_addr_t dma_addr, unsigned int buff_size) +{ + if (dlp_drv.is_dma_capable) { + dma_free_coherent(dlp_drv.controller, + buff_size, buff, dma_addr); + } else { + if ((buff_size >= PAGE_SIZE) + && ((buff_size & (PAGE_SIZE - 1)) == 0)) { + free_pages((unsigned int)buff, get_order(buff_size)); + } else { + kfree(buff); + } + } +} + +/** + * dlp_pdu_alloc - helper function to allocate and initialise a new pdu + * @hsi_channel: the HSI channel number + * @ttype: pdu transfer type READ/WRITE + * @buffer_size: pdu buffer size + * @nb_entries: number of entries in the Scatter Gather table + * @user_data: pdu context data (user data) + * @complete_cb: xfer complete callback + * @destruct_cb: pdu destruct callback + * + * Returns a reference to the newly created pdu or NULL if an error occured. + */ +struct hsi_msg *dlp_pdu_alloc(unsigned int hsi_channel, + int ttype, + int buffer_size, + int nb_entries, + void *user_data, + xfer_complete_cb complete_cb, + xfer_complete_cb destruct_cb) +{ + struct hsi_msg *new; + void *buffer; + int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL; + + PROLOG("%d, %s, size:%d", + hsi_channel, + (ttype == HSI_MSG_WRITE) ? "TX" : "RX", buffer_size); + + new = hsi_alloc_msg(nb_entries, flags); + if (!new) { + CRITICAL("No more memory to allocate hsi_msg struct"); + goto out; + } + + /* Allocate data buffer */ + buffer = dlp_buffer_alloc(buffer_size, &sg_dma_address(new->sgt.sgl)); + if (!buffer) { + CRITICAL("No more memory to allocate hsi_msg data buffer"); + goto fail; + } + + sg_set_buf(new->sgt.sgl, buffer, buffer_size); + + new->cl = dlp_drv.client; + new->channel = hsi_channel; + new->ttype = ttype; + new->context = user_data; + new->complete = complete_cb; + new->destructor = destruct_cb; + + EPILOG("0x%p", new); + return new; + +fail: + hsi_free_msg(new); + +out: + EPILOG("Failed"); + return NULL; +} + +/** + * dlp_pdu_free - helper function to delete and free an existing pdu + * @pdu: a reference to the pdu to delete + * @pdu_size: the real pdu size (allocated size) + * + * This function shall only be called by the pool of pdu management routines. + */ +void dlp_pdu_free(struct hsi_msg *pdu, unsigned int pdu_size) +{ + PROLOG("0x%p, %s, pdu_size: %d, sgl->length: %d", + pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX", + pdu_size, pdu->sgt.sgl->length); + + /* Revert to the real allocated size */ + pdu->sgt.sgl->length = pdu_size; + + /* Free the data buffer */ + dlp_buffer_free(sg_virt(pdu->sgt.sgl), + sg_dma_address(pdu->sgt.sgl), pdu_size); + + hsi_free_msg(pdu); + + EPILOG(); +} + +/** + * dlp_pdu_delete - recycle or free a pdu + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * @pdu: a reference to the pdu to delete + * + * This function is either recycling the pdu if there are not too many pdus + * in the system, otherwise destroy it and free its resource. + */ +void dlp_pdu_delete(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu) +{ + int full; + + PROLOG("all_len: %d, wait_max: %d, ctrl_max: %d, pdu [0x%p, %s]", + xfer_ctx->all_len, xfer_ctx->wait_max, xfer_ctx->ctrl_max, + pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX"); + + full = (xfer_ctx->all_len > xfer_ctx->wait_max + xfer_ctx->ctrl_max); + + if (full) { + dlp_pdu_free(pdu, xfer_ctx->channel->pdu_size); + + xfer_ctx->all_len--; + } else { + pdu->status = HSI_STATUS_COMPLETED; + pdu->actual_len = 0; + pdu->break_frame = 0; + + xfer_ctx->room += dlp_pdu_room_in(pdu); + + /* Recycle the pdu */ + dlp_fifo_recycled_push(xfer_ctx, pdu); + } + + EPILOG("all_len: %d, wait_max: %d, ctrl_max: %d", + xfer_ctx->all_len, xfer_ctx->wait_max, xfer_ctx->ctrl_max); +} + +/** + * dlp_pdu_recycle - pdu recycling helper function for the RX side + * @xfer_ctx: a reference to the RX context where recycled pdus FIFO sits + * @pdu: a reference to the pdu that shall be recycled + * + * This helper method is recycling the pdu and pushing a new pdu to the + * controller if there is room available in the controller FIFO, and finally + * updating the state of the RX state machine. + */ +void dlp_pdu_recycle(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu) +{ + unsigned long flags; + int have_space; + + PROLOG("0x%p, ctrl_len: %d", pdu, xfer_ctx->ctrl_len); + + /* Recycle or Free the pdu */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_pdu_delete(xfer_ctx, pdu); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + /* The CTRL still have space ? */ + read_lock_irqsave(&xfer_ctx->lock, flags); + have_space = xfer_ctx->ctrl_len < xfer_ctx->ctrl_max; + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (have_space) { + struct hsi_msg *new; + int ret; + + /* Get a recycled pdu */ + new = dlp_fifo_recycled_pop(xfer_ctx); + + /* Push the pdu */ + ret = dlp_hsi_controller_push(xfer_ctx, new); + if (ret) + dlp_fifo_recycled_push(xfer_ctx, new); + } + + dlp_ctx_update_state(xfer_ctx); + EPILOG(); +} + +/** + * dlp_pdu_check_header - Check the DLP header (Signature) + * @pdu: a reference to the considered pdu + * + * Returns TRUE if the PDU signature is invalid + */ +inline int dlp_pdu_header_valid(struct hsi_msg *pdu) +{ + u32 *header = sg_virt(pdu->sgt.sgl); + + return ((header[0] & 0xFFFF0000) == DLP_HEADER_SIGNATURE); +} + +/** + * dlp_pdu_set_header - Write the DLP header (Signature+Sequence number) + * @pdu: a reference to the considered pdu + * @xfer_ctx: a reference to the xfer context + * + */ +static inline void dlp_pdu_set_header(struct dlp_xfer_ctx *xfer_ctx, + struct hsi_msg *pdu) +{ + u32 *header = sg_virt(pdu->sgt.sgl); + + header[0] = (DLP_HEADER_SIGNATURE | (xfer_ctx->seq_num & 0xFFFF)); +} + +/** + * dlp_pdu_get_offset - Get the offset information from the eDLP header + * @pdu: a reference to the considered pdu + * + * Returns the offset information to encode in the header + */ +unsigned int dlp_pdu_get_offset(struct hsi_msg *pdu) +{ + u32 *header = (u32 *)(sg_virt(pdu->sgt.sgl)); + + return header[1]; +} + +/** + * dlp_pdu_get_length - Get the length information from the eDLP header + * @pdu: a reference to the considered pdu + * + * Returns the length information to encode in the header + */ +inline unsigned int dlp_pdu_get_length(struct hsi_msg *pdu) +{ + u32 *header = (u32 *)(sg_virt(pdu->sgt.sgl)); + + return (header[2] & 0x3FFFF); +} + + +/** + * dlp_pdu_update - initialise a pdu for entering the RX wait FIFO + * @ch_ctx: a reference to related channel context + * @pdu: a reference to the considered pdu + * + * This helper function is simply updating the scatterlist information. + */ +void dlp_pdu_update(struct dlp_channel *ch_ctx, struct hsi_msg *pdu) +{ + struct scatterlist *sg = pdu->sgt.sgl; + + /* Use a non null pdu length when an error occur to forward it to + * the upper layers. + * Do not use the in-pdu length which can be broken */ + if ((!pdu->break_frame) && (pdu->status == HSI_STATUS_COMPLETED)) { + pdu->actual_len = dlp_pdu_get_length(pdu); + } else { + CRITICAL + ("Invalid RX pdu (0x%p) status [status: %d, break_frame: %d," + " actual_len: %d", pdu, pdu->status, pdu->break_frame, + pdu->actual_len); + + pdu->actual_len = 1; + } + + /* If the decoded frame size is invalid, we have a big trouble */ + if ((!pdu->actual_len) || (pdu->actual_len > ch_ctx->pdu_size)) { + CRITICAL("Invalid pdu (0x%p) size (0x%X Bytes)", pdu, + pdu->actual_len); + + pdu->status = HSI_STATUS_ERROR; + pdu->actual_len = 1; + } + + sg->length = 0; +} + +/** + * dlp_pdu_reset - revert a pdu to a working order + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * @pdu: a reference to the considered pdu + * @length: the pdu length to set + * + * This helper function is simply updating the scatterlist information. + */ +inline void dlp_pdu_reset(struct dlp_xfer_ctx *xfer_ctx, + struct hsi_msg *pdu, unsigned int length) +{ + struct scatterlist *sg = pdu->sgt.sgl; + + PROLOG("0x%p", pdu); + + sg->offset -= sg->length; + sg->length = length; + pdu->actual_len = 0; + + EPILOG(); +}; + +/** + * dlp_pdu_room_in - helper function for getting current room in the pdu + * @pdu: a reference to the considered pdu + * + * Returns the room in byte in the current pdu + */ +inline unsigned int dlp_pdu_room_in(struct hsi_msg *pdu) +{ + unsigned int used_len = pdu->actual_len + DLP_TTY_HEADER_LENGTH; + + return (pdu->sgt.sgl->length - used_len); +} + +/** + * dlp_pdu_destructor - delete or recycle an existing pdu + * @pdu: a reference to the pdu to delete + * + * This function shall only be called as an HSI destruct callback. + */ +static void dlp_pdu_destructor(struct hsi_msg *pdu) +{ + struct dlp_xfer_ctx *xfer_ctx = pdu->context; + unsigned long flags; + + PROLOG("0x%p", pdu); + + /* Decrease the CTRL fifo size */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_hsi_controller_pop(xfer_ctx); + + /* Recycle or Free the pdu */ + dlp_pdu_delete(xfer_ctx, pdu); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (xfer_ctx->ttype == HSI_MSG_READ) + dlp_ctx_update_state(xfer_ctx); + else if (dlp_ctx_is_empty(xfer_ctx)) + wake_up(&xfer_ctx->channel->tx_empty_event); + + EPILOG(); +} + +/**************************************************************************** + * + * State handling helper + * + ***************************************************************************/ + +/** + * _dlp_ctx_get_state - get the global state of a state machine + * @xfer_ctx: a reference to the state machine context + * + * Returns the current state of the requested TX or RX context. + */ +static inline __must_check + unsigned int _dlp_ctx_get_state(struct dlp_xfer_ctx *xfer_ctx) +{ + return (xfer_ctx->state & DLP_GLOBAL_STATE_MASK); +} + +/** + * dlp_ctx_is_state - checks the global state of a state machine + * @xfer_ctx: a reference to the state machine context + * @state: the state to consider + * + * Returns a non-zero value if in the requested state. + */ +static inline __must_check int dlp_ctx_is_state(struct dlp_xfer_ctx *xfer_ctx, + unsigned int state) +{ + int is_state; + unsigned long flags; +#ifdef DEBUG + BUG_ON(state & ~DLP_GLOBAL_STATE_MASK); +#endif + + read_lock_irqsave(&xfer_ctx->lock, flags); + is_state = (_dlp_ctx_get_state(xfer_ctx) == state); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + return is_state; +} + +/** + * dlp_ctx_get_state - get the global state of a state machine + * @xfer_ctx: a reference to the state machine context + * + * Returns the current state of the requested TX or RX context. + * + * This version adds the spinlock guarding + */ +inline __must_check + unsigned int dlp_ctx_get_state(struct dlp_xfer_ctx *xfer_ctx) +{ + unsigned int state; + unsigned long flags; + + read_lock_irqsave(&xfer_ctx->lock, flags); + state = _dlp_ctx_get_state(xfer_ctx); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + return state; +} + +/** + * dlp_ctx_set_state - sets the global state of a state machine + * @xfer_ctx: a reference to the state machine context + * @state: the state to set + */ +inline void dlp_ctx_set_state(struct dlp_xfer_ctx *xfer_ctx, unsigned int state) +{ + unsigned long flags; + +#ifdef DEBUG + BUG_ON(state & ~DLP_GLOBAL_STATE_MASK); +#endif + + write_lock_irqsave(&xfer_ctx->lock, flags); + xfer_ctx->state = (xfer_ctx->state & ~DLP_GLOBAL_STATE_MASK) | state; + write_unlock_irqrestore(&xfer_ctx->lock, flags); +} + +/** + * dlp_ctx_has_flag - checks if a flag is present in the state + * @xfer_ctx: a reference to the state machine context + * @flag: the flag(s) to consider + * + * Returns a non-zero value if all requested flags are present. + */ +inline __must_check int dlp_ctx_has_flag(struct dlp_xfer_ctx *xfer_ctx, + unsigned int flag) +{ + unsigned long flags; + int has_flag; + +#ifdef DEBUG + BUG_ON(flag & DLP_GLOBAL_STATE_MASK); +#endif + + read_lock_irqsave(&xfer_ctx->lock, flags); + has_flag = ((xfer_ctx->state & flag) == flag); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + return has_flag; +} + +/** + * dlp_ctx_set_flag - flags some extra information in the state + * @xfer_ctx: a reference to the state machine context + * @flag: the flag(s) to set + */ +inline void dlp_ctx_set_flag(struct dlp_xfer_ctx *xfer_ctx, unsigned int flag) +{ + unsigned long flags; + +#ifdef DEBUG + BUG_ON(flag & DLP_GLOBAL_STATE_MASK); +#endif + + write_lock_irqsave(&xfer_ctx->lock, flags); + xfer_ctx->state |= flag; + write_unlock_irqrestore(&xfer_ctx->lock, flags); +} + +/** + * dlp_ctx_clear_flag - unflags some extra information in the state + * @xfer_ctx: a reference to the state machine context + * @flag: the flag(s) to clear + */ +inline void dlp_ctx_clear_flag(struct dlp_xfer_ctx *xfer_ctx, unsigned int flag) +{ + unsigned long flags; + +#ifdef DEBUG + BUG_ON(flag & DLP_GLOBAL_STATE_MASK); +#endif + + write_lock_irqsave(&xfer_ctx->lock, flags); + xfer_ctx->state &= ~flag; + write_unlock_irqrestore(&xfer_ctx->lock, flags); +} + +/** + * dlp_ctx_is_empty_safe - checks if a context is empty (all FIFO are empty) + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * + * This helper function is returning a non-zero value if both the wait FIFO and + * the controller FIFO are empty. + */ +int dlp_ctx_is_empty(struct dlp_xfer_ctx *xfer_ctx) +{ + int ret; + unsigned long flags; + + PROLOG(); + + read_lock_irqsave(&xfer_ctx->lock, flags); + ret = ((xfer_ctx->wait_len == 0) && (xfer_ctx->ctrl_len == 0)); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG(); + return ret; +} + +/** + * dlp_ctx_have_credits - checks if a context have credits + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * @ch_ctx: a reference to the channel context + * + * This helper function returns TRUE in these cases: + * - RX context + * - TX context with credits available. + * - The driver is used for loopback test mode (for debug). + */ +int dlp_ctx_have_credits(struct dlp_xfer_ctx *xfer_ctx, + struct dlp_channel *ch_ctx) +{ + int have_credits = 0; + unsigned long flags; + + PROLOG(); + + spin_lock_irqsave(&ch_ctx->lock, flags); + if ((xfer_ctx->ttype == HSI_MSG_READ) || + ((xfer_ctx->ttype == HSI_MSG_WRITE) && (ch_ctx->credits))) { + have_credits = 1; + } + spin_unlock_irqrestore(&ch_ctx->lock, flags); + + EPILOG("%d", have_credits); + return have_credits; +} + +/** + * dlp_ctx_update_status - updating the channel status further to config + * changes (channel, pdu length) + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + */ +void dlp_ctx_update_status(struct dlp_xfer_ctx *xfer_ctx) +{ + struct list_head *curr; + struct hsi_msg *pdu; + struct scatterlist *sg; + unsigned long flags; + + PROLOG(); + + read_lock_irqsave(&xfer_ctx->lock, flags); + + list_for_each(curr, &xfer_ctx->recycled_pdus) { + pdu = list_entry(curr, struct hsi_msg, link); + pdu->channel = xfer_ctx->channel->hsi_channel; + + sg = pdu->sgt.sgl; + + // FIXME: To be removed !!! + sg->length = xfer_ctx->payload_len + DLP_TTY_HEADER_LENGTH; + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + write_lock_irqsave(&xfer_ctx->lock, flags); + xfer_ctx->room += xfer_ctx->payload_len; + xfer_ctx->room -= (sg->length - DLP_TTY_HEADER_LENGTH); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + read_lock_irqsave(&xfer_ctx->lock, flags); + } + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (xfer_ctx->ttype == HSI_MSG_WRITE) + dlp_drv.client->tx_cfg = xfer_ctx->config; + else + dlp_drv.client->rx_cfg = xfer_ctx->config; + + EPILOG(); +} + +/** + * dlp_ctx_update_state_not_active - update the RX state machine upon reception of an + * @xfer_ctx: a reference to the RX context to consider + * + * This helper function updates the RX state in accordance with the status of + * the RX FIFO. + */ +static inline void dlp_ctx_update_state_not_active(struct dlp_xfer_ctx + *xfer_ctx) +{ + PROLOG(); + + if (!dlp_ctx_is_empty(xfer_ctx)) + dlp_ctx_set_state(xfer_ctx, TTY); + else + dlp_ctx_set_state(xfer_ctx, IDLE); + + EPILOG(); +} + +/** + * dlp_ctx_update_state - update the RX state machine upon recycling of a + * RX pdu + * @xfer_ctx: a reference to the xfer RX context to consider + * + * This helper function updates the RX state in accordance with the status of + * the RX FIFO, unless the RX is required active. + */ +static inline void dlp_ctx_update_state(struct dlp_xfer_ctx *xfer_ctx) +{ + PROLOG(); + + if (!dlp_ctx_is_state(xfer_ctx, ACTIVE)) + dlp_ctx_update_state_not_active(xfer_ctx); + + EPILOG(); +} + +/**************************************************************************** + * + * FIFO common functions + * + ***************************************************************************/ + +/** + * dlp_fifo_tail - get a reference to the last pdu of a FIFO or NULL + * if the FIFO is empty + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * @fifo: a reference of the FIFO to consider + * + * Returns a reference to the last pdu of this FIFO or NULL if the FIFO is + * empty. + */ +inline __must_check + struct hsi_msg *dlp_fifo_tail(struct dlp_xfer_ctx *xfer_ctx, + struct list_head *fifo) +{ + struct hsi_msg *pdu = NULL; + + /* Empty ? */ + if (! list_empty(fifo)) { + pdu = list_entry(fifo->prev, struct hsi_msg, link); + } + + return pdu; +} + +/** + * dlp_fifo_empty - deletes the whole content of a FIFO + * @fifo: a reference to the FIFO to empty + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * + * This helper function is emptying a FIFO and deleting all its pdus. + */ +static void dlp_fifo_empty(struct list_head *fifo, + struct dlp_xfer_ctx *xfer_ctx) +{ + struct hsi_msg *pdu; + unsigned long flags; + + PROLOG(); + + write_lock_irqsave(&xfer_ctx->lock, flags); + + /* Empty ? */ + if (list_empty(fifo)) + goto out; + + while ((pdu = list_entry(fifo->next, struct hsi_msg, link))) { + /* Remove the pdu from the list */ + list_del_init(&pdu->link); + + /* pdu free */ + dlp_pdu_free(pdu, xfer_ctx->channel->pdu_size); + } + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + out: + EPILOG(); +} + +/**************************************************************************** + * + * Wait FIFO handling methods + * + ***************************************************************************/ + +/** + * dlp_fifo_wait_pop - pop a pdu from the FIFO of waiting pdus + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * + * This function is not only popping the pdu, but also updating the counters + * related to the FIFO of waiting pdus in the considered context. + */ +struct hsi_msg *dlp_fifo_wait_pop(struct dlp_xfer_ctx *xfer_ctx) +{ + struct list_head *fifo = &xfer_ctx->wait_pdus; + struct hsi_msg *pdu = NULL; + + PROLOG("wait_len: %d, pdu: 0x%p", xfer_ctx->wait_len, pdu); + + /* Check if the list was not flushed */ + if (list_empty(fifo)) + goto out; + + /* Get the list head */ + pdu = list_entry(fifo->next, struct hsi_msg, link); + + /* Remove the pdu from the list */ + list_del_init(&pdu->link); + + xfer_ctx->wait_len --; + xfer_ctx->buffered -= pdu->actual_len; + +out: + + EPILOG("wait_len: %d", xfer_ctx->wait_len); + return pdu; +} + +/** + * dlp_fifo_wait_push - push a pdu to the FIFO of waiting pdus + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * @pdu: a reference to the pdu to push + * + * This function is not only pushing the pdu, but also updating the counters + * related to the FIFO of waiting pdus in the considered context. + */ +inline void dlp_fifo_wait_push(struct dlp_xfer_ctx *xfer_ctx, + struct hsi_msg *pdu) +{ + unsigned long flags; + + PROLOG("wait_len: %d, pdu [0x%p, %s]", xfer_ctx->wait_len, + pdu, (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX"); + + write_lock_irqsave(&xfer_ctx->lock, flags); + + xfer_ctx->wait_len++; + xfer_ctx->buffered += pdu->actual_len; + + /* at the end of the list */ + list_add_tail(&pdu->link, &xfer_ctx->wait_pdus); + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG("wait_len: %d", xfer_ctx->wait_len); +} + +/** + * dlp_fifo_wait_push_back - push back a pdu in the FIFO of waiting pdus + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * @pdu: a reference to the pdu to push back + * + * This function is not only pushing back the pdu, but also updating the + * counters related to the FIFO of waiting pdus in the considered context. + */ +inline void dlp_fifo_wait_push_back(struct dlp_xfer_ctx *xfer_ctx, + struct hsi_msg *pdu) +{ + unsigned long flags; + + PROLOG("wait_len: %d, pdu: 0x%p", xfer_ctx->wait_len, pdu); + + write_lock_irqsave(&xfer_ctx->lock, flags); + + xfer_ctx->wait_len++; + xfer_ctx->buffered += pdu->actual_len; + + /* at the begining of the list */ + list_add(&pdu->link, &xfer_ctx->wait_pdus); + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG("wait_len: %d", xfer_ctx->wait_len); +} + +/** + * dlp_pop_wait_push_ctrl - transfer the first TX pdu from the wait FIFO to + * the controller FIFO + * @xfer_ctx: a reference to the TX context to consider + * @check_pdu: to check if the pdu is being updated (marked as break pdu) + * + * This wrapper function is simply transferring the first pdu of the wait + * FIFO. + */ +void dlp_pop_wait_push_ctrl(struct dlp_xfer_ctx *xfer_ctx, + unsigned int check_pdu) +{ + int ok = 1; + unsigned long flags; + struct hsi_msg *pdu; + + PROLOG("check_pdu: %d", check_pdu); + + write_lock_irqsave(&xfer_ctx->lock, flags); + pdu = dlp_fifo_wait_pop(xfer_ctx); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (!pdu) + goto out; + + if (check_pdu) { + ok = !pdu->break_frame; + } + + if (ok) { + /* Note that no error is returned upon transfer failure, + * in such cases, the pdu is simply returned back to the + * wait FIFO, as nothing else can be done + */ + int ret; + unsigned int actual_len = pdu->actual_len; + + /* */ + ret = dlp_hsi_controller_push(xfer_ctx, pdu); + if (ret) { + if (ret == -EACCES) { /* port released */ + + write_lock_irqsave(&xfer_ctx->lock, flags); + xfer_ctx->room -= dlp_pdu_room_in(pdu); + + /* Recycle or Free the pdu */ + dlp_pdu_delete(xfer_ctx, pdu); + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + } else { + /* Push back the pdu */ + dlp_fifo_wait_push_back(xfer_ctx, pdu); + } + } +#ifdef CONFIG_HSI_DLP_TTY_STATS + else { + xfer_ctx->tty_stats.data_sz += actual_len; + xfer_ctx->tty_stats.pdus_cnt++; + } +#endif + } + + out: + EPILOG(); +} + +/**************************************************************************** + * + * Frame recycling helper functions + * + ***************************************************************************/ + +static inline void dlp_fifo_recycled_push(struct dlp_xfer_ctx *xfer_ctx, + struct hsi_msg *pdu) +{ + PROLOG("pdu [0x%p, %s, sgl->len: %d]", + pdu, + (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX", + pdu->sgt.sgl->length); + + /* at the end of the list */ + list_add_tail(&pdu->link, &xfer_ctx->recycled_pdus); + + EPILOG(); +} + +/** + * dlp_fifo_recycled_pop - creating a new empty file from the recycling FIFO + * @xfer_ctx: a reference to the xfer context (RX or TX) to consider + * + * Returns a reference to the new empty pdu or NULL if there are no recycled + * pdus left. + */ +struct hsi_msg *dlp_fifo_recycled_pop(struct dlp_xfer_ctx *xfer_ctx) +{ + struct hsi_msg *pdu = NULL; + struct list_head *first; + unsigned long flags; + + PROLOG(); + + read_lock_irqsave(&xfer_ctx->lock, flags); + first = &xfer_ctx->recycled_pdus; + + /* Empty ? */ + if (first != first->next) { + /* Get the fist pdu */ + pdu = list_entry(first->next, struct hsi_msg, link); + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + /* Remove the pdu from the list */ + write_lock_irqsave(&xfer_ctx->lock, flags); + list_del_init(&pdu->link); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG("pdu [0x%p, %s, %d]", + pdu, + (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX", + pdu->sgt.sgl->length); + } else { + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG("pdu [NULL]"); + } + + return pdu; +} + +/** + * dlp_pop_recycled_push_ctrl - Push as many recycled pdus as possible to the + * controller FIFO + * @xfer_ctx: a reference to the RX context where the FIFO of recycled pdus sits + * + * Returns 0 upon success or an error code. + * + * This helper method is returning 0 on success, or an error code. + */ +__must_check int dlp_pop_recycled_push_ctrl(struct dlp_xfer_ctx *xfer_ctx) +{ + int ret = 0; + struct hsi_msg *new; + unsigned long flags; + + PROLOG(); + + read_lock_irqsave(&xfer_ctx->lock, flags); + + while (xfer_ctx->ctrl_len < xfer_ctx->ctrl_max) { + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + new = dlp_fifo_recycled_pop(xfer_ctx); + if (!new) { + ret = -ENOMEM; + goto out; + } + + ret = dlp_hsi_controller_push(xfer_ctx, new); + if (ret) { + dlp_fifo_recycled_push(xfer_ctx, new); + + ret = -EAGAIN; + goto out; + } + + read_lock_irqsave(&xfer_ctx->lock, flags); + } + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + +out: + EPILOG("ret: %d", ret); + return ret; +} + +/**************************************************************************** + * + * HSI Controller + * + ***************************************************************************/ + +/** + * dlp_hsi_controller_push - push a pdu to the HSI controller FIFO + * @xfer_ctx: a reference to the xfer context (TX or RX) to consider + * @pdu: a reference to the pdu to push + * + * Returns 0 on success or an error code on failure. + * + * This function is not only pushing the pdu, but also updating the counters + * related to the FIFO of outstanding pdus in the considered context. + */ +int dlp_hsi_controller_push(struct dlp_xfer_ctx *xfer_ctx, struct hsi_msg *pdu) +{ + unsigned int lost_room = dlp_pdu_room_in(pdu); + struct dlp_channel *ch_ctx = xfer_ctx->channel; + unsigned long flags; + int err = 0; + + PROLOG("ctrl_len: %d, credits: %d, pdu [0x%p, %s, %d]", + xfer_ctx->ctrl_len, xfer_ctx->channel->credits, + pdu, + (pdu->ttype == HSI_MSG_WRITE) ? "TX" : "RX", + pdu->sgt.sgl->length); + + /* Check credits */ + if (!dlp_ctx_have_credits(xfer_ctx, ch_ctx)) { + CRITICAL("NO credits avaible"); + goto out; + } + + /* Decrease counters values */ + write_lock_irqsave(&xfer_ctx->lock, flags); + + xfer_ctx->room -= lost_room; + xfer_ctx->ctrl_len++; + + if (pdu->ttype == HSI_MSG_WRITE) + xfer_ctx->channel->credits--; + + xfer_ctx->seq_num++; + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + /* Set the DLP signature + seq_num */ + dlp_pdu_set_header(xfer_ctx, pdu); + + /* Dump the PDU */ + if (pdu->ttype == HSI_MSG_WRITE) { + dlp_pdu_dump(pdu, 0); + //dlp_dbg_dump_pdu(pdu, 16, 160, 0); + } + + err = hsi_async(pdu->cl, pdu); + if (!err) { + if ((pdu->ttype == HSI_MSG_WRITE) && (xfer_ctx->ctrl_len)) { + mod_timer(&ch_ctx->hangup_timer, + jiffies + ch_ctx->hangup_delay); + } + } else { + unsigned int ctrl_len; + + CRITICAL("hsi_async() failed (%d)", err); + + /* Failed to send pdu, set back counters values */ + write_lock_irqsave(&xfer_ctx->lock, flags); + + xfer_ctx->seq_num--; + xfer_ctx->room += lost_room; + xfer_ctx->ctrl_len--; + + if (pdu->ttype == HSI_MSG_WRITE) + xfer_ctx->channel->credits++; + + ctrl_len = xfer_ctx->ctrl_len; + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (!ctrl_len) { + struct dlp_channel *ch_ctx = xfer_ctx->channel; + del_timer(&ch_ctx->hangup_timer); + } + } + +out: + EPILOG("ctrl_len: %d, credits: %d", + xfer_ctx->ctrl_len, xfer_ctx->channel->credits); + return err; +} + +/** + * dlp_hsi_controller_pop - pop a pdu from the HSI controller FIFO + * @xfer_ctx: a reference to the xfer context (TX or RX) to consider + * + * This function is only updating the counters related to the FIFO of + * outstanding pdus in the considered context. + */ +inline void dlp_hsi_controller_pop(struct dlp_xfer_ctx *xfer_ctx) +{ + PROLOG("ctrl_len: %d", xfer_ctx->ctrl_len); + + xfer_ctx->ctrl_len--; + + EPILOG("ctrl_len: %d", xfer_ctx->ctrl_len); +} + +/** + * dlp_hsi_start_tx - update the TX state machine on every new transfer + * @xfer_ctx: a reference to the TX context to consider + * + * This helper function updates the TX state if it is currently idle and + * inform the HSI pduwork and attached controller. + */ +void dlp_hsi_start_tx(struct dlp_xfer_ctx *xfer_ctx) +{ + PROLOG(); + + /* we found the case that we called hsi_port_shutdown and + * stop_tx, while the tty want to flush data, in this case + * just ignore the start_tx, and the code in ctl push already + * ignore the flush and discard the packet. + */ + if (!hsi_port_claimed(dlp_drv.client)) + return; + + if (dlp_ctx_is_state(xfer_ctx, IDLE)) { + dlp_ctx_set_state(xfer_ctx, ACTIVE); + + hsi_start_tx(dlp_drv.client); + } else { + del_timer(&xfer_ctx->timer); + } + + EPILOG(); +} + +/** + * dlp_stop_tx - update the TX state machine after expiration of the TX active + * timeout further to a no outstanding TX transaction status + * @xfer_ctx: a reference to the TX context to consider + * + * This helper function updates the TX state if it is currently active and + * inform the HSI pduwork and attached controller. + */ +void dlp_stop_tx(struct dlp_xfer_ctx *xfer_ctx) +{ + PROLOG(); + + if (dlp_ctx_is_state(xfer_ctx, ACTIVE)) { + dlp_ctx_set_state(xfer_ctx, IDLE); + + hsi_stop_tx(dlp_drv.client); + } + + EPILOG(); +} + +/** + * dlp_stop_rx - update the internal RX state machine + * @xfer_ctx: a reference to the RX context to consider + * @ch_ctx: a reference to related channel context + * + * This helper function updates the RX state and allows the HSI device to + * sleep. + */ +inline void dlp_stop_rx(struct dlp_xfer_ctx *xfer_ctx, + struct dlp_channel *ch_ctx) +{ + PROLOG(); + + dlp_ctx_update_state_not_active(xfer_ctx); + + EPILOG(); +} + +/** + * dlp_hsi_port_claim - Claim & setup the HSI port + * @ch_ctx: a reference to related channel context + */ +int dlp_hsi_port_claim(struct dlp_channel *ch_ctx) +{ + int ret = 0; + + //PROLOG("hsi_ch: %d", ch_ctx->hsi_channel); + PROLOG(); + + /* Claim the HSI port (if not claimed already) */ + if (atomic_inc_return(&dlp_drv.busy) != 1) + goto out; + + /* Claim the HSI port */ + ret = hsi_claim_port(dlp_drv.client, 1); + if (unlikely(ret)) { + CRITICAL("hsi_claim_port() failed (%d)", ret); + goto out; + } + + /* Setup the HSI controller */ + ret = hsi_setup(dlp_drv.client); + if (unlikely(ret)) { + CRITICAL("hsi_setup() failed (%d)", ret); + hsi_release_port(dlp_drv.client); + goto out; + } + +out: + EPILOG(); + return ret; +} + +/** + * dlp_hsi_port_unclaim - UnClaim (release) the HSI port + * @ch_ctx: a reference to related channel context + */ +inline void dlp_hsi_port_unclaim(struct dlp_channel *ch_ctx) +{ + //PROLOG("hsi_ch: %d", ch_ctx->hsi_channel); + PROLOG(); + + if (atomic_dec_and_test(&dlp_drv.busy)) + hsi_release_port(dlp_drv.client); + + EPILOG(); +} + +/** + * dlp_hsi_start_rx_cb - update the internal RX state machine + * @cl: a reference to HSI client to consider + * + * This helper function updates the RX state and wakes the device. + */ +static void dlp_hsi_start_rx_cb(struct hsi_client *cl) +{ + // FIXME: only ch_ctx[1] + struct dlp_channel *ch_ctx = (struct dlp_channel *)hsi_client_drvdata(cl); + struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->rx; + + PROLOG(); + + dlp_ctx_set_state(xfer_ctx, ACTIVE); + + EPILOG(); +} + +/** + * dlp_hsi_stop_rx_cb - update the internal RX state machine + * @cl: a reference to HSI client to consider + * + * This helper function updates the RX state and allows the HSI device to + * sleep. + */ +static void dlp_hsi_stop_rx_cb(struct hsi_client *cl) +{ + // FIXME: only ch_ctx[1] + struct dlp_channel *ch_ctx = (struct dlp_channel *)hsi_client_drvdata(cl); + struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->rx; + + PROLOG(); + + dlp_stop_rx(xfer_ctx, ch_ctx); + + EPILOG(); +} + +/**************************************************************************** + * + * RX/TX xfer contexts + * + ***************************************************************************/ + +/** + * dlp_increase_pdus_pool - background work aimed at creating new pdus + * @work: a reference to the work context + * + * This function is called as a background job (in the dlp_recycle_wq work + * queue) for performing the pdu resource allocation (which can then sleep). + * + * An error message is sent upon the failure of DLP_PDU_ALLOC_RETRY_MAX_CNT + * allocation requests. + */ +static void dlp_increase_pdus_pool(struct work_struct *work) +{ + struct dlp_xfer_ctx *xfer_ctx = container_of(work, struct dlp_xfer_ctx, + increase_pool); + struct hsi_msg *new; + unsigned long flags; + int retry; + + PROLOG(); + + read_lock_irqsave(&xfer_ctx->lock, flags); + + while (xfer_ctx->all_len < (xfer_ctx->wait_max + xfer_ctx->ctrl_max)) { + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + retry = 0; + new = dlp_pdu_alloc(xfer_ctx->channel->hsi_channel, + xfer_ctx->ttype, + xfer_ctx->channel->pdu_size, + 1, + xfer_ctx, + xfer_ctx->complete_cb, dlp_pdu_destructor); + + while (!new) { + ++retry; + if (retry == DLP_PDU_ALLOC_RETRY_MAX_CNT) { + CRITICAL("Cannot allocate a pdu after %d retries...", retry); + retry = 0; + } + + /* No memory available: do something more urgent ! */ + schedule(); + + new = dlp_pdu_alloc(xfer_ctx->channel->hsi_channel, + xfer_ctx->ttype, + xfer_ctx->channel->pdu_size, + 1, + xfer_ctx, + xfer_ctx->complete_cb, + dlp_pdu_destructor); + } + + write_lock_irqsave(&xfer_ctx->lock, flags); + xfer_ctx->room += xfer_ctx->payload_len; + xfer_ctx->all_len++; + + dlp_fifo_recycled_push(xfer_ctx, new); + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + read_lock_irqsave(&xfer_ctx->lock, flags); + } + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG("%s pdu's pool created (hsi_channel: %d)", + (xfer_ctx->ttype == HSI_MSG_WRITE ? "TX" : "RX"), + xfer_ctx->channel->hsi_channel); +} + +/** + * dlp_xfer_ctx_init - initialise a TX or RX context after its creation + * @ch_ctx: a reference to its related channel context + * @xfer_ctx: a reference to the considered TX or RX context + * @delay: the initial delay for the timer related to the TX or RX context + * @wait_max: the maximal size of the wait FIFO for this context + * @ctrl_max: the maximal size of the HSI controller FIFO for this context + * @complete_cb: HSI transfer complete callback + * @ttype: HSI transfer type + * + * This helper function is simply filling in the initial data into a newly + * created TX or RX context. + */ +void dlp_xfer_ctx_init(struct dlp_channel *ch_ctx, + struct dlp_xfer_ctx *xfer_ctx, + unsigned int delay, + unsigned int wait_max, + unsigned int ctrl_max, + xfer_complete_cb complete_cb, unsigned int ttype) +{ + PROLOG("delay: %d, wait_max: %d, ctrl_max: %d", + delay, wait_max, ctrl_max); + + INIT_LIST_HEAD(&xfer_ctx->wait_pdus); + INIT_LIST_HEAD(&xfer_ctx->recycled_pdus); + + init_timer(&xfer_ctx->timer); + rwlock_init(&xfer_ctx->lock); + + xfer_ctx->timer.data = (unsigned long)xfer_ctx; + xfer_ctx->delay = from_usecs(delay); + xfer_ctx->state = IDLE; + xfer_ctx->wait_max = wait_max; + xfer_ctx->ctrl_max = ctrl_max; + xfer_ctx->channel = ch_ctx; + xfer_ctx->payload_len = DLP_TTY_PAYLOAD_LENGTH; + xfer_ctx->ttype = ttype; + xfer_ctx->complete_cb = complete_cb; + INIT_WORK(&xfer_ctx->increase_pool, dlp_increase_pdus_pool); + + EPILOG(); +} + +/** + * dlp_xfer_ctx_clear - clears a TX or RX context prior to its deletion + * @xfer_ctx: a reference to the considered TX or RX context + * + * This helper function is simply calling the relevant destructors + * and reseting the context information. + */ +void dlp_xfer_ctx_clear(struct dlp_xfer_ctx *xfer_ctx) +{ + unsigned long flags; + + PROLOG(); + + write_lock_irqsave(&xfer_ctx->lock, flags); + + xfer_ctx->wait_max = 0; + xfer_ctx->ctrl_max = 0; + xfer_ctx->state = IDLE; + del_timer(&xfer_ctx->timer); + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + dlp_fifo_empty(&xfer_ctx->wait_pdus, xfer_ctx); + dlp_fifo_empty(&xfer_ctx->recycled_pdus, xfer_ctx); + + flush_work(&xfer_ctx->increase_pool); + + EPILOG(); +} + +/**************************************************************************** + * + * Protocol driver functions + * + ***************************************************************************/ + +/** + * dlp_driver_cleanup - Release driver allocated resources + * + * This helper function calls the "delete" function for each + * channel context. + */ +static void dlp_driver_cleanup(void) +{ + int i = 0; + /* Contexts allocation functions */ + /* NOTE : this array should be aligned with the context enum + * defined in the .h file */ + dlp_context_delete delete_funcs[DLP_CHANNEL_COUNT] = { + dlp_ctrl_ctx_delete, /* 0 : CTRL */ + dlp_tty_ctx_delete, /* 1 : TTY */ + dlp_net_ctx_delete, /* 2 : NET */ + dlp_net_ctx_delete, /* 3 : NET */ + dlp_net_ctx_delete}; /* 4 : NET */ + + PROLOG(); + + /* Free DLP contexts */ + for (i = 0; i < DLP_CHANNEL_COUNT; i++) { + if (dlp_drv.channels[i]) { + delete_funcs[i] (dlp_drv.channels[i]); + dlp_drv.channels[i] = NULL; + + } + } + + EPILOG(); +} + +/** + * dlp_driver_probe - creates a new context in the DLP driver + * @dev: a reference to the HSI device requiring a context + * + * Returns 0 upon success or an error in case of an error + * + * This function is creating a new context per HSI controller requiring a + * DLP protocol driver, creates the related TTY port and TTY entry in the + * filesystem. + */ +static int __init dlp_driver_probe(struct device *dev) +{ + /* The parent of our device is the HSI port, + * the parent of the HSI port is the HSI controller device */ + struct device *controller = dev->parent->parent; + struct hsi_client *client = to_hsi_client(dev); + int i, ret = 0; + + /* Contexts allocation functions */ + /* NOTE : this array should be aligned with the context enum + * defined in the .h file */ + dlp_context_create create_funcs[DLP_CHANNEL_COUNT] = { + dlp_ctrl_ctx_create, /* 0 : CTRL */ + dlp_tty_ctx_create, /* 1 : TTY */ + dlp_net_ctx_create, /* 2 : NET */ + dlp_net_ctx_create, /* 3 : NET */ + dlp_net_ctx_create}; /* 4 : NET */ + + PROLOG(); + + /* Save the controller & client */ + dlp_drv.controller = controller; + dlp_drv.client = client; + dlp_drv.is_dma_capable = is_device_dma_capable(controller); + spin_lock_init(&dlp_drv.lock); + + /* Warn if no DMA capability */ + if (!dlp_drv.is_dma_capable) { + WARNING("HSI device is not DMA capable"); + } + + /* FIXME: Claim the HSI port */ + ret = dlp_hsi_port_claim(NULL); + if (ret) { + goto out; + } + + /* Create DLP contexts */ + for (i = 0; i < DLP_CHANNEL_COUNT; i++) { + dlp_drv.channels[i] = create_funcs[i] (i, dev); + if (!dlp_drv.channels[i]) { + goto cleanup; + } + } + + /* */ + client->hsi_start_rx = dlp_hsi_start_rx_cb; + client->hsi_stop_rx = dlp_hsi_stop_rx_cb; + + /* FIXME : other channels ? */ + hsi_client_set_drvdata(client, dlp_drv.channels[DLP_CHANNEL_TTY]); + + /* Create /proc/hsi-dlp */ + // FIXME + //if (dlp_drv.debug) + { + proc_create_data(DRVNAME, S_IRUGO, NULL, &dlp_proc_ops, NULL); + } + + EPILOG(); + return 0; + +cleanup: + dlp_driver_cleanup(); + +out: + EPILOG("%d", ret); + return ret; +} + +/** + * dlp_driver_remove - removes a context from the DLP driver + * @dev: a reference to the device requiring the context + * + * Returns 0 on success or an error code + * + * This function is freeing all resources hold by the context attached to the + * requesting HSI device. + */ +static int __exit dlp_driver_remove(struct device *dev) +{ + struct hsi_client *client = to_hsi_client(dev); + + PROLOG(); + + client->hsi_start_rx = NULL; + client->hsi_stop_rx = NULL; + hsi_client_set_drvdata(client, NULL); + + dlp_driver_cleanup(); + + /* UnClaim the HSI port */ + dlp_hsi_port_unclaim(NULL); + + EPILOG(); + return 0; +} + +/* + * dlp_driver_setup - configuration of the DLP driver + */ +static struct hsi_client_driver dlp_driver_setup = { + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + .probe = dlp_driver_probe, + .remove = dlp_driver_remove, + }, +}; + +/** + * dlp_module_init - initialises the DLP driver common parts + * + * Returns 0 on success or an error code + */ +static int __init dlp_module_init(void) +{ + int err; + + /* Initialization */ + memset(&dlp_drv, 0, sizeof(struct dlp_driver)); + + /* FIXME : to be removed */ + dlp_drv.debug = 0x0; + + PROLOG(); + + /* Create the workqueue for allocating pdus */ + dlp_drv.recycle_wq = create_singlethread_workqueue(DRVNAME "-wq"); + if (unlikely(!dlp_drv.recycle_wq)) { + CRITICAL("Unable to create pool-handling workqueue"); + err = -EFAULT; + goto out; + } + + /* Create the workqueue for tx hangup */ + dlp_drv.tx_hangup_wq = create_singlethread_workqueue(DRVNAME "-hwq"); + if (unlikely(!dlp_drv.tx_hangup_wq)) { + CRITICAL("Unable to create tx hangup workqueue"); + err = -EFAULT; + goto del_wq; + } + + /* Not used yet (claimed) */ + atomic_set(&dlp_drv.busy, 0); + + /* Now, register the client */ + err = hsi_register_client_driver(&dlp_driver_setup); + if (unlikely(err)) { + CRITICAL("hsi_register_client_driver() failed (%d)", err); + goto del_2wq; + } + + EPILOG("driver initialized"); + return 0; + +del_2wq: + destroy_workqueue(dlp_drv.tx_hangup_wq); +del_wq: + destroy_workqueue(dlp_drv.recycle_wq); + +out: + EPILOG("Failed"); + return err; +} + +/** + * dlp_driver_exit - frees the resources taken by the DLP driver common parts + */ +static void __exit dlp_module_exit(void) +{ + PROLOG(); + + destroy_workqueue(dlp_drv.recycle_wq); + destroy_workqueue(dlp_drv.tx_hangup_wq); + + hsi_unregister_client_driver(&dlp_driver_setup); + + EPILOG("driver removed"); +} + +module_init(dlp_module_init); +module_exit(dlp_module_exit); + +MODULE_AUTHOR("Olivier Stoltz Douchet "); +MODULE_AUTHOR("Faouaz Tenoutit "); +MODULE_DESCRIPTION("LTE protocol driver over HSI for IMC modems"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0-HSI-LTE"); diff --git a/drivers/hsi/clients/dlp_main.h b/drivers/hsi/clients/dlp_main.h new file mode 100644 index 0000000..9bb5aef --- /dev/null +++ b/drivers/hsi/clients/dlp_main.h @@ -0,0 +1,505 @@ +/* + * dlp_main.h + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI + * protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef _DLP_MAIN_H_ +#define _DLP_MAIN_H_ + +#include +#include +#include +#include + +#include "../dlp_debug.h" + +#define DRVNAME "hsi-dlp" + +/* Defaut TX timeout delay (in microseconds) */ +#define DLP_HANGUP_DELAY 1000000 + +/* Defaut HSI TX delay (in microseconds) */ +#define DLP_HSI_TX_DELAY 10000 + +/* Defaut HSI RX delay (in microseconds) */ +#define DLP_HSI_RX_DELAY 10000 + +/* Maximal number of pdu allocation failure prior firing an error message */ +#define DLP_PDU_ALLOC_RETRY_MAX_CNT 10 + +/* Delays for powering up/resetting the modem (in milliseconds) */ +#define DLP_POWER_ON_INTERLINE_DELAY 1 +#define DLP_POWER_ON_POST_DELAY 200 + +/* Round-up the pdu and header length to a multiple of 4-bytes to align + * on the HSI 4-byte granularity*/ +#define DLP_PDU_LENGTH (((CONFIG_HSI_DLP_PDU_LENGTH+3)/4)*4) +#define DLP_HEADER_LENGTH (((CONFIG_HSI_DLP_HEADER_LENGTH+3)/4)*4) +#define DLP_PAYLOAD_LENGTH (DLP_PDU_LENGTH-DLP_HEADER_LENGTH) +#define DLP_LENGTH_MASK (roundup_pow_of_two(DLP_PAYLOAD_LENGTH)-1) + +/* Initial minimal buffering size (in bytes) */ +#define DLP_MIN_TX_BUFFERING 65536 +#define DLP_MIN_RX_BUFFERING 65536 + +/* Compute the TX and RX, FIFO depth from the buffering requirements */ +/* For optimal performances the DLP_HSI_TX_CTRL_FIFO size shall be set to 2 at + * least to allow back-to-back transfers. */ +#define DLP_TX_MAX_LEN ((DLP_MIN_TX_BUFFERING+DLP_PAYLOAD_LENGTH-1)/DLP_PAYLOAD_LENGTH) +#define DLP_RX_MAX_LEN ((DLP_MIN_RX_BUFFERING+DLP_PAYLOAD_LENGTH-1)/DLP_PAYLOAD_LENGTH) + +#define DLP_HSI_TX_CTRL_FIFO 2 +#define DLP_HSI_TX_WAIT_FIFO max(DLP_TX_MAX_LEN-DLP_HSI_TX_CTRL_FIFO, 1) + +#define DLP_HSI_RX_WAIT_FIFO max(DLP_RX_MAX_LEN/2, 1) +#define DLP_HSI_RX_CTRL_FIFO max(DLP_RX_MAX_LEN-DLP_HSI_RX_WAIT_FIFO, 1) + +/* Tag for detecting buggy pdu sizes (must be greater than the maximum pdu + * size */ +#define DLP_BUGGY_PDU_SIZE 0xFFFFFFFFUL + +/* PDU size for TTY channel */ +#define DLP_TTY_PDU_LENGTH 4096 /* 1500 Bytes */ +#define DLP_TTY_HEADER_LENGTH 16 +#define DLP_TTY_PAYLOAD_LENGTH (DLP_TTY_PDU_LENGTH - DLP_TTY_HEADER_LENGTH) + +/* PDU size for NET channels */ +#define DLP_NET_PDU_SIZE 4096 /* 15360: 15 KBytes */ + +/* PDU size for CTRL channel */ +#define DLP_CTRL_PDU_SIZE 4 /* 4 Bytes */ + +/* Alignment params */ +#define DLP_PACKET_ALIGN_AP 16 +#define DLP_PACKET_ALIGN_CP 16 + +/* Header space params */ +#define DLP_HDR_SPACE_AP 16 +#define DLP_HDR_SPACE_CP 16 + +/* Header signature */ +#define DLP_HEADER_SIGNATURE 0xF9A80000 + +/* header fields */ +#define DLP_HDR_DATA_SIZE(sz) ((sz) & 0x3FFFF) +#define DLP_HDR_MORE_DESC (0x1 << 31) +#define DLP_HDR_NO_MORE_DESC (0x0 << 31) + +#define DLP_HDR_MIDDLE_OF_PACKET (0x0 << 29) +#define DLP_HDR_END_OF_PACKET (0x1 << 29) +#define DLP_HDR_START_OF_PACKET (0x2 << 29) +#define DLP_HDR_COMPLETE_PACKET (0x3 << 29) + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +/* RX and TX state machine definitions */ +enum { + IDLE, + ACTIVE, + TTY, +}; + +/* DLP contexts */ +enum { + DLP_CHANNEL_CTRL, /* HSI Channel 0 */ + DLP_CHANNEL_TTY, /* HSI Channel 1 */ + DLP_CHANNEL_NET1, /* HSI Channel 2 */ + DLP_CHANNEL_NET2, /* HSI Channel 3 */ + DLP_CHANNEL_NET3, /* HSI Channel 4 */ + + DLP_CHANNEL_COUNT +}; + +/* */ +#define DLP_GLOBAL_STATE_SZ 2 +#define DLP_GLOBAL_STATE_MASK ((1< + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dlp_main.h" + +#define DEBUG_TAG 0x8 +#define DEBUG_VAR dlp_drv.debug + +/* Defaut HSI TX timeout delay (in microseconds) */ +#define NET_HANGUP_DELAY 1000000 /* 1 sec */ + +/* Defaut NET stack TX timeout delay (in milliseconds) */ +#define DLP_NET_TX_DELAY 20000 /* 20 sec */ + +/* + * struct dlp_net_context - NET channel private data + * + * @ndev: Registred network device + * @net_padd: Padding buffer + * @net_padd_dma: Padding buffer dma address + */ +struct dlp_net_context { + struct net_device *ndev; + + /* Padding buffer */ + void *net_padd; + dma_addr_t net_padd_dma; +}; + +/* + * + * + */ +struct dlp_net_tx_params { + struct dlp_channel *ch_ctx; + struct sk_buff *skb; +}; + +/* + * + * LOCAL functions + * + **/ + +/* + * + * + */ +static void dlp_net_modem_hangup(struct dlp_channel *ch_ctx, int reason) +{ + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + + PROLOG(); + + ch_ctx->hangup_reason |= reason; + + /* Stop the NET IF */ + if (!netif_queue_stopped(net_ctx->ndev)) + netif_stop_queue(net_ctx->ndev); + + EPILOG(); +} + +/** + * dlp_net_mdm_coredump_cb - Modem has signaled a core dump + * @irq: interrupt number + * @dev: our device pointer + * + * The modem has indicated a core dump. + */ +static void dlp_net_mdm_coredump_cb(struct dlp_channel *ch_ctx) +{ + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + + PROLOG("%s", net_ctx->ndev->name); + + dlp_net_modem_hangup(ch_ctx, DLP_MODEM_HU_COREDUMP); + + EPILOG(); +} + +/** + * dlp_net_mdm_reset_cb - Modem has changed reset state + * @data: channel pointer + * + * The modem has either entered or left reset state. Check the GPIO + * line to see which. + */ +static void dlp_net_mdm_reset_cb(struct dlp_channel *ch_ctx) +{ + PROLOG(); + + dlp_net_modem_hangup(ch_ctx, DLP_MODEM_HU_RESET); + + EPILOG(); +} + +/** + * dlp_net_credits_available_cb - TX credits are available + * @data: channel pointer + */ +static void dlp_net_credits_available_cb(struct dlp_channel *ch_ctx) +{ + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + + PROLOG(); + + /* Restart the NET stack if it was stopped */ + if (netif_queue_stopped(net_ctx->ndev)) + netif_wake_queue(net_ctx->ndev); + + EPILOG(); +} + +/** + * dlp_net_type_trans + * + */ +static __be16 dlp_net_type_trans(const char *buffer) +{ + if (!buffer) + return htons(0); + + /* Look at IP version field */ + switch ((*buffer) >> 4) { + case 4: + return htons(ETH_P_IP); + case 6: + return htons(ETH_P_IPV6); + default: + CRITICAL("Invalid IP frame header"); + } + + return htons(0); +} + +/** + * dlp_net_complete_tx - bottom-up flow for the TX side + * @pdu: a reference to the completed pdu + * + * A TX transfer has completed: recycle the completed pdu and kick a new + * delayed request or enter the IDLE state if nothing else is expected. + */ +static void dlp_net_complete_tx(struct hsi_msg *pdu) +{ + unsigned long flags; + struct dlp_net_tx_params *msg_param = pdu->context; + struct dlp_channel *ch_ctx = msg_param->ch_ctx; + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->tx; + + PROLOG("%s", net_ctx->ndev->name); + + /* TX done, free the skb */ + dev_kfree_skb(msg_param->skb); + + /* Dump the PDU */ + // dlp_pdu_dump(pdu, 1); + + /* Update statistics */ + net_ctx->ndev->stats.tx_bytes += pdu->actual_len; + net_ctx->ndev->stats.tx_packets++; + + /* Free the pdu */ + dlp_pdu_free(pdu, pdu->sgt.sgl->length); + + /* Decrease the CTRL fifo size */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_hsi_controller_pop(xfer_ctx); + + /* Decrease the pdus counter */ + xfer_ctx->all_len--; + + /* Still have queued TX pdu ? */ + if (xfer_ctx->ctrl_len) { + mod_timer(&ch_ctx->hangup_timer, + jiffies + ch_ctx->hangup_delay); + } else { + del_timer(&ch_ctx->hangup_timer); + } + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + EPILOG(); +} + +/* + * Receive a packet: retrieve, encapsulate and pass over to upper levels + */ +static void dlp_net_complete_rx(struct hsi_msg *pdu) +{ + struct sk_buff *skb; + struct dlp_xfer_ctx *xfer_ctx = pdu->context; + struct dlp_channel *ch_ctx = xfer_ctx->channel; + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + unsigned int more_packets, data_size, ret, offset; + unsigned char *skb_data, *data_addr, *start_addr; + unsigned int *ptr; + unsigned long flags; + + PROLOG("%s, pdu [0x%p, actual_len: %d, sgl->len: %d]", + net_ctx->ndev->name, pdu, pdu->actual_len, pdu->sgt.sgl->length); + + ret = 0; + + /* Dump the first 160 bytes */ + dlp_dbg_dump_pdu(pdu, 16, 160, 0); + + /* Pop the CTRL queue */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_hsi_controller_pop(xfer_ctx); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + /* Get the data pointer */ + ptr = sg_virt(pdu->sgt.sgl); + + /* Read the header */ + /*--------------------*/ + if (!dlp_pdu_header_valid(pdu)) { + CRITICAL("Invalid PDU signature (0x%x)", (*ptr)); + ret = -EINVAL; + goto out; + } + + /* Read packets desc */ + /*---------------------*/ + start_addr = (unsigned char *)ptr; /* Skip the header */ + + do { + /* Get the start offset */ + ptr++; + offset = (*ptr); + + /* Get the size & address */ + ptr++; + more_packets = (*ptr) & DLP_HDR_MORE_DESC; + data_size = DLP_HDR_DATA_SIZE((*ptr)) - DLP_HDR_SPACE_AP; + data_addr = start_addr + offset + DLP_HDR_SPACE_AP; + + PRINT_RX + ("RX: DESC => data_addr: 0x%p, offset: 0x%x, size: %d\n", + data_addr, offset, data_size); + + /* Dump the first 160 bytes */ + // dlp_dbg_dump_data_as_byte(data_addr, MIN(data_size, 160), 16, 0); + + /* + * The packet has been retrieved from the transmission + * medium. Build an skb around it, so upper layers can handle it + */ + skb = netdev_alloc_skb_ip_align(net_ctx->ndev, data_size); + if (!skb) { + CRITICAL + ("No more memory (data_size: %d) - packet dropped\n", + data_size); + + net_ctx->ndev->stats.rx_dropped++; + goto out; + } + + skb_data = skb_put(skb, data_size); + memcpy(skb_data, data_addr, data_size); + + skb->dev = net_ctx->ndev; + skb_reset_mac_header(skb); + skb->protocol = dlp_net_type_trans(skb_data); + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + + /* Dump the first 160 bytes */ + //dlp_dbg_dump_data_as_byte(skb->data, MIN(skb->len, 160), 16, 0); + + /* Push received packet up to the IP networking stack */ + ret = netif_rx(skb); + + /* Update statistics */ + if (ret) { + WARNING("Packet dropped"); + net_ctx->ndev->stats.rx_dropped++; + + /* Free the allocated skb */ + /* FIXME : to be freed ???? */ + } else { + net_ctx->ndev->stats.rx_bytes += data_size; + net_ctx->ndev->stats.rx_packets++; + } + } + while (more_packets); + + ret = 0; + +out: + /* Recycle the RX pdu */ + dlp_pdu_recycle(xfer_ctx, pdu); + + EPILOG("%d", ret); +} + +/** + * dlp_net_hangup_timer_cb - timer function for tx timeout hangup request + * @param: a reference to the channel to consider + */ +static void dlp_net_hangup_timer_cb(unsigned long int param) +{ + struct dlp_channel *ch_ctx = (struct dlp_channel *)param; + + PROLOG(); + + CRITICAL("Hangup (Timeout) !"); + + ch_ctx->hangup_reason |= DLP_MODEM_HU_TIMEOUT; + queue_work(dlp_drv.tx_hangup_wq, &ch_ctx->hangup_queue); + + EPILOG(); +} + +/** + * dlp_net_hsi_tx_timeout - work queue function called from hangup timer + * @work: a reference to work queue element + * + */ +static void dlp_net_hsi_tx_timeout(struct work_struct *work) +{ + //struct dlp_channel *ch_ctx = container_of(work, struct dlp_channel, + // hangup_queue); + + /* FIXME : TBD */ + + CRITICAL("HSI TX timeout"); +} + +/* + * + * NETWORK INTERFACE functions + * + **/ +int dlp_net_open(struct net_device *dev) +{ + int ret; + struct dlp_channel *ch_ctx = netdev_priv(dev); + + PROLOG("%s, hsi_ch:%d", dev->name, ch_ctx->hsi_channel); + + /* Check the modem readiness */ + if (! dlp_ctrl_modem_is_ready()) { + CRITICAL("Unale to open NETWORK IF (Modem NOT ready) !"); + ret = -EBUSY; + goto out; + } + + /* Claim the HSI port */ + ret = dlp_hsi_port_claim(ch_ctx); + if (ret) { + goto out; + } + + // FIXME: To be removed (done in dlp_net_ctx_create) + ret = dlp_ctrl_send_open_conn_cmd(ch_ctx); + if (ret) { + CRITICAL("dlp_ctrl_send_open_conn_cmd() failed !"); + ret = -EIO; + goto unclaim; + } + + /* Push all RX pdus */ + ret = dlp_pop_recycled_push_ctrl(&ch_ctx->rx); + + /* Start the netif */ + netif_wake_queue(dev); + + EPILOG(); + return ret; + +unclaim: + dlp_hsi_port_unclaim(ch_ctx); + +out: + EPILOG(); + return ret; +} + +int dlp_net_stop(struct net_device *dev) +{ + int ret; + struct dlp_channel *ch_ctx = netdev_priv(dev); + struct dlp_xfer_ctx *tx_ctx; + struct dlp_xfer_ctx *rx_ctx; + + PROLOG("%s, hsi_ch:%d", dev->name, ch_ctx->hsi_channel); + + tx_ctx = &ch_ctx->tx; + rx_ctx = &ch_ctx->rx; + + del_timer_sync(&ch_ctx->hangup_timer); + + /* Stop the NET IF */ + if (!netif_queue_stopped(dev)) + netif_stop_queue(dev); + + // FIXME: To be removed (should be done in dlp_net_ctx_delete) + ret = dlp_ctrl_send_cancel_conn_cmd(ch_ctx); + if (ret) { + CRITICAL("dlp_ctrl_send_cancel_conn_cmd() failed !"); + } + + del_timer_sync(&ch_ctx->hangup_timer); + hsi_flush(dlp_drv.client); + + /* RX */ + del_timer_sync(&rx_ctx->timer); + dlp_stop_rx(rx_ctx, ch_ctx); + + /* TX */ + del_timer_sync(&tx_ctx->timer); + dlp_stop_tx(tx_ctx); + + dlp_ctx_set_state(tx_ctx, IDLE); + + /* Release the HSI port */ + dlp_hsi_port_unclaim(ch_ctx); + + EPILOG(); + return 0; +} + +static void dlp_net_pdu_destructor(struct hsi_msg *pdu) +{ + struct dlp_xfer_ctx *xfer_ctx = pdu->context; + struct dlp_channel *ch_ctx = xfer_ctx->channel; + + PROLOG(); + + dlp_pdu_free(pdu, ch_ctx->pdu_size); + + EPILOG(); +} + +/* + * Transmit a packet + */ +static int dlp_net_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct dlp_channel *ch_ctx = netdev_priv(dev); + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + struct dlp_net_tx_params *msg_param; + int i, ret, nb_padding, nb_entries, nb_packets; + unsigned char *skb_data; + unsigned int *ptr; + unsigned int skb_len, padding_len, offset, desc_size, align_size; + struct hsi_msg *new; + struct scatterlist *sg; + skb_frag_t *frag; + + PROLOG("%s, len:%d, nb_frag: %d", + dev->name, skb->len, skb_shinfo(skb)->nr_frags); + + if (!dlp_ctx_have_credits(&ch_ctx->tx, ch_ctx)) { + /* Stop the NET if */ + netif_stop_queue(net_ctx->ndev); + + CRITICAL("No credits available (%d)", ch_ctx->tx.seq_num); + ret = NETDEV_TX_BUSY; + goto out; + } + + /* Dump the first 160 bytes */ + // dlp_dbg_dump_data_as_byte(skb->data, MIN(skb->len, 160), 16); + + if (skb->len < ETH_ZLEN) { + // WARNING("Padding received packet (too small size: %d)", skb->len); + + if (skb_padto(skb, ETH_ZLEN)) { + return NETDEV_TX_OK; + } + } + + /* Set msg params */ + msg_param = (struct dlp_net_tx_params *)skb->cb; + msg_param->ch_ctx = ch_ctx; + msg_param->skb = skb; + + /* Save the timestamp */ + dev->trans_start = jiffies; + + /* Set the IP header */ +#if 0 // FIXME : Check if it is V4/6 + struct iphdr *iph; + + iph = ip_hdr(skb); + iph->check = 0; + + { + unsigned int saddr, daddr; + char *s, *d; + +#define BYTE(b) (((int)b)&0xff) + + saddr = htonl(iph->saddr); + daddr = htonl(iph->daddr); + + s = (char *)&saddr; + d = (char *)&daddr; + + PDEBUG("\n\tver: 0x%x, ihl: 0x%x, tos: 0x%x, tot_len: %d\n" + "\tid : 0x%x, frag_off: 0x%x\n" + "\tttl: 0x%x, protocol: 0x%x, check: 0x%x\n" + "\tsaddr: [%u.%u.%u.%u]\n" + "\tdaddr: [%u.%u.%u.%u]\n", + iph->version, iph->ihl, iph->tos, ntohs(iph->tot_len), + ntohs(iph->id), ntohs(iph->frag_off), + iph->ttl, iph->protocol, iph->check, + BYTE(s[3]), BYTE(s[2]), BYTE(s[1]), BYTE(s[0]), + BYTE(d[3]), BYTE(d[2]), BYTE(d[1]), BYTE(d[0])); + } + + iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); +#endif + + /* Compute the number of needed padding entries */ + nb_padding = 1; + if (skb_has_frag_list(skb)) { + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + /* Size aligned ? */ + nb_padding += (IS_ALIGNED(skb_shinfo(skb)->frags[i].size,DLP_PACKET_ALIGN_CP) ? 0 : 1); + } + } + + /* Compute the number of needed SGT entries */ + nb_packets = skb_shinfo(skb)->nr_frags + 1; + nb_entries = 1 + /* Header */ + nb_packets + /* Packets */ + nb_padding; /* Padding */ + + /* Allocate the HSI msg */ + new = hsi_alloc_msg(nb_entries, GFP_ATOMIC); + if (!new) { + CRITICAL("No more memory to allocate hsi_msg struct"); + ret = NETDEV_TX_BUSY; + goto out; + } + + new->cl = dlp_drv.client; + new->channel = ch_ctx->hsi_channel; + new->ttype = HSI_MSG_WRITE; + new->context = msg_param; + new->complete = dlp_net_complete_tx; + new->destructor = dlp_net_pdu_destructor; + + /* Allocate the header buffer */ + sg = new->sgt.sgl; + + desc_size = 1 * 4 + /* Signature + Seq_num */ + nb_packets * 8; /* Packets Offset+Size */ + + desc_size = ALIGN(desc_size, DLP_PACKET_ALIGN_CP); + desc_size += DLP_HDR_SPACE_CP; + + ptr = dlp_buffer_alloc(desc_size, &sg_dma_address(sg)); + if (!ptr) { + CRITICAL("No more memory to allocate msg descriptors"); + goto free_msg; + } + + /* Set the header buffer */ + /*-----------------------*/ + sg_set_buf(sg, ptr, desc_size); + + /* Write packets desc */ + /*---------------------*/ + i = 0; + offset = desc_size - DLP_HDR_SPACE_CP; + + PRINT_TX("TX: desc_size: 0x%x, nb_entries:%d, nb_packets:%d\n", + desc_size, nb_entries, nb_packets); + + do { + if (nb_packets == 1) { + skb_data = skb->data; + skb_len = skb->len; + } else { + frag = &skb_shinfo(skb)->frags[i]; + + skb_len = frag->size; + skb_data = (void *)frag->page; + + CRITICAL("FRAGGGGGGGGGGGGGGGGGGGGG"); + } + + /* Set the start offset */ + ptr++; + (*ptr) = offset; + + /* Set the size */ + ptr++; + (*ptr) = (DLP_HDR_NO_MORE_DESC | DLP_HDR_COMPLETE_PACKET | skb_len); + (*ptr) += DLP_HDR_SPACE_CP; + + /* Set the packet SG entry */ + sg = sg_next(sg); + sg_set_buf(sg, skb_data, skb_len); + sg->dma_address = dma_map_single(dlp_drv.controller, + skb_data, + skb_len, DMA_TO_DEVICE); + + PRINT_TX("TX: Entry %d: offset: 0x%x, size:0x%x\n", i, offset, + (*ptr)); + + /* Still have packets ? */ + i++; + if (i < nb_packets) { + /* Need padding ? */ + align_size = ALIGN(skb_len, DLP_PACKET_ALIGN_CP); + if (align_size != skb_len) { + skb_data = net_ctx->net_padd; + skb_len = align_size - skb_len; + + sg = sg_next(sg); + sg_set_buf(sg, skb_data, skb_len); + + sg->dma_address = + dma_map_single(dlp_drv.controller, skb_data, + skb_len, DMA_TO_DEVICE); + } + + /* Update the offset value */ + offset += align_size + DLP_HDR_SPACE_CP; + } else { + /* Update the offset value */ + offset += skb_len + DLP_HDR_SPACE_CP; + } + } + while (i < nb_packets); + + /* Write the padding entry (Check 4 bytes alignment) */ + /*---------------------------------------------------*/ + padding_len = ch_ctx->pdu_size - offset; + padding_len = (padding_len / 4) * 4; + if (padding_len) { + sg = sg_next(sg); + sg_set_buf(sg, net_ctx->net_padd, padding_len); + sg->dma_address = net_ctx->net_padd_dma; + } + + PRINT_TX("TX: Entry %d (Padding): offset: 0x%x, size:0x%x\n", i, offset, + padding_len); + +#if 0 + /* */ + u32 first_len, first_mapping; + int frag, first_entry = entry; + + BUG_ON(1); + + /* give this initial chunk to the */ + first_len = skb_headlen(skb); + first_mapping = dma_map_single(hp->dma_dev, skb->data, first_len, + DMA_TO_DEVICE); + entry = NEXT_TX(entry); + + for (frag = 0; frag < skb_shinfo(skb)->nr_frags; frag++) { + skb_frag_t *this_frag = &skb_shinfo(skb)->frags[frag]; + u32 len, mapping, this_txflags; + + len = this_frag->size; + mapping = dma_map_page(hp->dma_dev, this_frag->page, + this_frag->page_offset, len, + DMA_TO_DEVICE); + // Process + + entry = NEXT_TX(entry); + } +#endif + + ret = dlp_hsi_controller_push(&ch_ctx->tx, new); + if (ret) { + ret = NETDEV_TX_BUSY; + goto free_msg; + } + + ret = NETDEV_TX_OK; + EPILOG("%d", ret); + return ret; + +free_msg: + hsi_free_msg(new); + +out: + EPILOG("%d", ret); + return ret; +} + +/* + * Deal with a transmit timeout. + */ +void dlp_net_tx_timeout(struct net_device *dev) +{ + struct dlp_channel *ch_ctx = netdev_priv(dev); + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + + PROLOG("%s", dev->name); + + PDEBUG("NET TX timeout at %d ms (latency: %d ms)", + jiffies_to_msecs(jiffies), + jiffies_to_msecs(jiffies - dev->trans_start)); + + /* Update statistics */ + net_ctx->ndev->stats.tx_errors++; + + EPILOG(); +} + +/* + * + */ +int dlp_net_change_mtu(struct net_device *dev, int new_mtu) +{ + int ret = -EPERM; + + PROLOG("%s", dev->name); + + EPILOG(); + return ret; +} + +static const struct net_device_ops dlp_net_netdev_ops = { + .ndo_open = dlp_net_open, + .ndo_stop = dlp_net_stop, + .ndo_start_xmit = dlp_net_start_xmit, + .ndo_change_mtu = dlp_net_change_mtu, + .ndo_tx_timeout = dlp_net_tx_timeout, +}; + +/* + * + * INIT function + * + **/ +void dlp_net_dev_setup(struct net_device *dev) +{ + PROLOG(); + + dev->netdev_ops = &dlp_net_netdev_ops; + dev->watchdog_timeo = DLP_NET_TX_DELAY; + + /* fill in the other fields */ +// dev->features = NETIF_F_SG | NETIF_F_NO_CSUM; // FIXME: wget is KO + + dev->type = ARPHRD_NONE; + dev->mtu = DLP_NET_PDU_SIZE; // FIXME: check wget crash + dev->tx_queue_len = 10; + dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + + EPILOG(); +} + +/**************************************************************************** + * + * Exported functions + * + ***************************************************************************/ + +struct dlp_channel *dlp_net_ctx_create(unsigned int index, struct device *dev) +{ + struct hsi_client *client = to_hsi_client(dev); + struct dlp_channel *ch_ctx; + struct net_device *ndev; + struct dlp_net_context *net_ctx; + int ret; + + PROLOG("%d", index); + + /* Allocate the net device */ + ndev = alloc_netdev(sizeof(struct dlp_channel), + CONFIG_HSI_DLP_NET_NAME "%d", dlp_net_dev_setup); + + if (!ndev) { + CRITICAL("alloc_netdev() failed !"); + goto out; + } + + /* Allocate the context private data */ + net_ctx = kzalloc(sizeof(struct dlp_net_context), GFP_KERNEL); + if (!net_ctx) { + CRITICAL("Unable to allocate memory (net_ctx)"); + goto free_dev; + } + + /* Allocate the padding buffer */ + net_ctx->net_padd = dlp_buffer_alloc(DLP_NET_PDU_SIZE, + &net_ctx->net_padd_dma); + + if (!net_ctx->net_padd) { + CRITICAL("No more memory to allocate padding buffer"); + ret = -ENOMEM; + goto out; + } + + /* Register the net device */ + ret = register_netdev(ndev); + if (ret) { + CRITICAL("register_netdev() for %s failed, error %d\n", + ndev->name, ret); + goto free_dev; + } + + net_ctx->ndev = ndev; + ch_ctx = netdev_priv(ndev); + + /* Save params */ + ch_ctx->ch_data = net_ctx; + ch_ctx->hsi_channel = index; + ch_ctx->credits = 0; + ch_ctx->pdu_size = DLP_NET_PDU_SIZE; + ch_ctx->rx.config = client->rx_cfg; + ch_ctx->tx.config = client->tx_cfg; + + spin_lock_init(&ch_ctx->lock); + init_timer(&ch_ctx->hangup_timer); + init_waitqueue_head(&ch_ctx->tx_empty_event); + INIT_WORK(&ch_ctx->hangup_queue, dlp_net_hsi_tx_timeout); + + ch_ctx->hangup_delay = from_usecs(NET_HANGUP_DELAY); + ch_ctx->hangup_timer.function = dlp_net_hangup_timer_cb; + ch_ctx->hangup_timer.data = (unsigned long int)ch_ctx; + + ch_ctx->modem_coredump_cb = dlp_net_mdm_coredump_cb; + ch_ctx->modem_reset_cb = dlp_net_mdm_reset_cb; + ch_ctx->credits_available_cb = dlp_net_credits_available_cb; + + dlp_xfer_ctx_init(ch_ctx, &ch_ctx->tx, + DLP_HSI_TX_DELAY, + DLP_HSI_TX_WAIT_FIFO, DLP_HSI_TX_CTRL_FIFO, + dlp_net_complete_tx, HSI_MSG_WRITE); + + dlp_xfer_ctx_init(ch_ctx, &ch_ctx->rx, + DLP_HSI_RX_DELAY, + DLP_HSI_RX_WAIT_FIFO, DLP_HSI_RX_CTRL_FIFO, + dlp_net_complete_rx, HSI_MSG_READ); + + /* FIXME : to be activated */ + /* Open the HSI channel */ + //ret = dlp_ctrl_send_open_conn_cmd(ch_ctx); + //if (ret) { + // CRITICAL("dlp_ctrl_send_open_conn_cmd() failed !"); + // goto free_dev; + //} + + /* Allocate RX FIFOs in background */ + queue_work(dlp_drv.recycle_wq, &ch_ctx->rx.increase_pool); + + EPILOG(); + return ch_ctx; + +free_dev: + free_netdev(ndev); + +out: + EPILOG("Failed"); + return NULL; +} + +int dlp_net_ctx_delete(struct dlp_channel *ch_ctx) +{ + int ret = 0; + struct dlp_net_context *net_ctx = ch_ctx->ch_data; + + del_timer_sync(&ch_ctx->hangup_timer); + + /* Unregister the net device */ + unregister_netdev(net_ctx->ndev); + + /* Delete the xfers context */ + dlp_xfer_ctx_clear(&ch_ctx->rx); + dlp_xfer_ctx_clear(&ch_ctx->tx); + + /* FIXME : to be activated */ + /* Release the HSI channel */ + // ret = dlp_ctrl_send_cancel_conn_cmd(ch_ctx); + //if (ret) { + // CRITICAL("dlp_ctrl_send_cancel_conn_cmd() failed !"); + //} + + /* Free the padding buffer */ + dlp_buffer_free(net_ctx->net_padd, + net_ctx->net_padd_dma, DLP_NET_PDU_SIZE); + + /* Free the ch_ctx */ + free_netdev(net_ctx->ndev); + + EPILOG(); + return ret; +} diff --git a/drivers/hsi/clients/dlp_tty.c b/drivers/hsi/clients/dlp_tty.c new file mode 100644 index 0000000..816c333 --- /dev/null +++ b/drivers/hsi/clients/dlp_tty.c @@ -0,0 +1,1621 @@ +/* + * dlp_tty.c + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI + * protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Olivier Stoltz Douchet + * Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dlp_main.h" + +#define DEBUG_TAG 0x4 +#define DEBUG_VAR dlp_drv.debug + +#define TTYNAME "tty"CONFIG_HSI_DLP_TTY_NAME + +#define RX_TTY_FORWARDING_BIT (1<ch_data; + + PROLOG(); + + ch_ctx->hangup_reason |= reason; + tty = tty_port_tty_get(&tty_ctx->tty_prt); + if (tty) { + tty_hangup(tty); + tty_kref_put(tty); + } + + EPILOG(); +} + +/** + * dlp_tty_mdm_coredump_cb - Modem has signaled a core dump + * @irq: interrupt number + * @dev: our device pointer + * + * The modem has indicated a core dump. + */ +static void dlp_tty_mdm_coredump_cb(struct dlp_channel *ch_ctx) +{ + PROLOG(); + + dlp_tty_modem_hangup(ch_ctx, DLP_MODEM_HU_COREDUMP); + + EPILOG(); +} + +/** + * dlp_tty_mdm_reset_cb - Modem has changed reset state + * @data: channel pointer + * + * The modem has either entered or left reset state. Check the GPIO + * line to see which. + */ +static void dlp_tty_mdm_reset_cb(struct dlp_channel *ch_ctx) +{ + PROLOG(); + + dlp_tty_modem_hangup(ch_ctx, DLP_MODEM_HU_RESET); + + EPILOG(); +} + +/** + * dlp_tty_pdu_data_ptr - helper function for getting the actual virtual address of + * a pdu data, taking into account the header offset + * @pdu: a reference to the considered pdu + * @offset: an offset to add to the current virtual address of the pdu data + * + * Returns the virtual base address of the actual pdu data + */ +inline __attribute_const__ + unsigned char *dlp_tty_pdu_data_ptr(struct hsi_msg *pdu, unsigned int offset) +{ + u32 *addr = sg_virt(pdu->sgt.sgl); + + /* Skip the Signature (+2) & Start address (+2) */ + addr += 4; + + return (((unsigned char *)addr) + offset); +} + +/** + * dlp_tty_pdu_set_length - write down the length information to the frame header + * @frame_ptr: a pointer to the virtual base address of a frame + * @sz: the length information to encode in the header + */ +inline void dlp_tty_pdu_set_length(struct hsi_msg *pdu, u32 sz) +{ + u32 *header = (u32 *) (sg_virt(pdu->sgt.sgl)); + header[1] = DLP_TTY_HEADER_LENGTH; + header[2] = DLP_HDR_NO_MORE_DESC | + DLP_HDR_COMPLETE_PACKET | DLP_HDR_DATA_SIZE(sz); +} + +/** + * dlp_tty_pdu_skip - skip a chunk of data at the beginning of a pdu + * @pdu: a reference to the considered pdu + * @copied: the length of the chunk to skip + * + * This helper function is simply updating the scatterlist information. + */ +inline void dlp_tty_pdu_skip(struct hsi_msg *pdu, unsigned int copied) +{ + struct scatterlist *sg = pdu->sgt.sgl; + + sg->offset += copied; + sg->length += copied; + pdu->actual_len -= copied; +}; + +/** + * dlp_tty_wakeup - wakeup an asleep TTY write function call + * @ch_ctx: a reference to the context related to this TTY + * + * This helper function awakes any asleep TTY write callback function. + */ +static void dlp_tty_wakeup(struct dlp_channel *ch_ctx) +{ + struct tty_struct *tty; + struct dlp_tty_context *tty_ctx = ch_ctx->ch_data; + + PROLOG(); + + tty = tty_port_tty_get(&tty_ctx->tty_prt); + if (likely(tty)) { + tty_wakeup(tty); + tty_kref_put(tty); + } + + EPILOG(); +} + +/** + * dlp_tty_forward - RX data TTY forwarding helper function + * @tty: a reference to the TTY where the data shall be forwarded + * @xfer_ctx: a reference to the RX context where the FIFO of waiting pdus sits + * + * Data contained in the waiting pdu FIFO shall be forwarded to the TTY. + * This function is : + * - Pushing as much data as possible to the TTY interface + * - Recycling pdus that have been fully forwarded + * - Kicking a TTY insert + * - Restart delayed job if some data is remaining in the waiting FIFO or if the + * controller FIFO is not full yet. + */ +static void dlp_tty_forward(struct tty_struct *tty, + struct dlp_xfer_ctx *xfer_ctx) +{ + struct hsi_msg *pdu; + unsigned long flags; + unsigned char *data_ptr; + unsigned int copied; + int do_push, ret; + char tty_flag; + + PROLOG(); + + if (dlp_ctx_has_flag(xfer_ctx, RX_TTY_FORWARDING_BIT)) { + dlp_ctx_set_flag(xfer_ctx, RX_TTY_REFORWARD_BIT); + return; + } + + /* Initialised to 1 to prevent unexpected TTY forwarding resume + * function when there is no TTY or when it is throttled */ + dlp_ctx_set_flag(xfer_ctx, RX_TTY_FORWARDING_BIT); + + copied = 1; + do_push = 0; + + del_timer(&xfer_ctx->timer); + +shoot_again_now: + + read_lock_irqsave(&xfer_ctx->lock, flags); + + while (xfer_ctx->wait_len > 0) { + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + write_lock_irqsave(&xfer_ctx->lock, flags); + pdu = dlp_fifo_wait_pop(xfer_ctx); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + if (!pdu) + goto no_more_tty_insert; + + if (pdu->status == HSI_STATUS_COMPLETED) + tty_flag = (likely(!pdu->break_frame)) ? + TTY_NORMAL : TTY_BREAK; + else + tty_flag = TTY_FRAME; + + if (unlikely(!tty)) + goto recycle_pdu; + + while (pdu->actual_len > 0) { + + if (test_bit(TTY_THROTTLED, &tty->flags)) { + /* Initialised to 1 to prevent unexpected TTY + * forwarding resume function schedule */ + copied = 1; + dlp_fifo_wait_push_back(xfer_ctx, pdu); + + goto no_more_tty_insert; + } + + /* Copy the data to the flip buffers */ + data_ptr = dlp_tty_pdu_data_ptr(pdu, 0); + copied = (unsigned int) + tty_insert_flip_string_fixed_flag(tty, + data_ptr, + tty_flag, + pdu->actual_len); + dlp_tty_pdu_skip(pdu, copied); + + /* We'll push the flip buffers each time something has + * been written to them to allow low latency */ + do_push |= (copied > 0); + + if (copied == 0) { + dlp_fifo_wait_push_back(xfer_ctx, pdu); + + goto no_more_tty_insert; + } + } + +recycle_pdu: + /* Reset the pdu offset & length */ + dlp_pdu_reset(xfer_ctx, + pdu, + xfer_ctx->payload_len + DLP_TTY_HEADER_LENGTH); + + /* Recycle or free the pdu */ + dlp_pdu_recycle(xfer_ctx, pdu); + + read_lock_irqsave(&xfer_ctx->lock, flags); + } + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + +no_more_tty_insert: + /* Schedule a flip since called from complete_rx() in an interrupt + * context instead of tty_flip_buffer_push() */ + if (do_push) + tty_schedule_flip(tty); + + /* If some reforwarding request occur in the meantime, do this now */ + if (dlp_ctx_has_flag(xfer_ctx, RX_TTY_REFORWARD_BIT)) { + dlp_ctx_clear_flag(xfer_ctx, RX_TTY_REFORWARD_BIT); + goto shoot_again_now; + } + + dlp_ctx_clear_flag(xfer_ctx, RX_TTY_FORWARDING_BIT); + + /* Push any available pud to the CTRL */ + ret = dlp_pop_recycled_push_ctrl(xfer_ctx); + + /* Shoot again later if there is still pending data to serve or if + * the RX controller FIFO is not full yet */ + if ((!copied) || (unlikely(ret == -EAGAIN))) + mod_timer(&xfer_ctx->timer, jiffies + xfer_ctx->delay); + + EPILOG(); +} + +/** + * dlp_tty_rx_forward_retry - TTY forwarding retry job + * @param: a casted reference to the to the RX context where the FIFO of + * waiting pdus sits + * + * This simply calls the TTY forwarding function in a tasklet shell. + */ +static void dlp_tty_rx_forward_retry(unsigned long param) +{ + struct dlp_xfer_ctx *xfer_ctx = (struct dlp_xfer_ctx *)param; + struct dlp_tty_context *tty_ctx = xfer_ctx->channel->ch_data; + struct tty_struct *tty; + + PROLOG(); + + tty = tty_port_tty_get(&tty_ctx->tty_prt); + + dlp_tty_forward(tty, xfer_ctx); + + if (tty) + tty_kref_put(tty); + + EPILOG(); +} + +/** + * dlp_tty_rx_forward_resume - TTY forwarding resume callback + * @tty: a reference to the TTY requesting the resume + * + * This simply calls the TTY forwarding function as a response to a TTY + * unthrottle event. + */ +static void dlp_tty_rx_forward_resume(struct tty_struct *tty) +{ + struct dlp_channel *ch_ctx; + + PROLOG(); + + /* Get the context reference from the driver data if already opened */ + ch_ctx = (struct dlp_channel *)tty->driver_data; + + if (!ch_ctx) + return; + + dlp_tty_forward(tty, &ch_ctx->rx); + + EPILOG(); +} + +/** + * dlp_tty_complete_tx - bottom-up flow for the TX side + * @pdu: a reference to the completed pdu + * + * A TX transfer has completed: recycle the completed pdu and kick a new + * delayed request to enter the IDLE state if nothing else is expected. + */ +static void dlp_tty_complete_tx(struct hsi_msg *pdu) +{ + struct dlp_xfer_ctx *xfer_ctx = pdu->context; + struct dlp_channel *ch_ctx = xfer_ctx->channel; + int pdu_available, wakeup = 0; + unsigned long flags; + + PROLOG(); + + dlp_drv.reset_ignore = 0; + + /* Dump the PDU */ + // dlp_pdu_dump(pdu, 1); + + /* Recycle or Free the pdu */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_pdu_delete(xfer_ctx, pdu); + + /* Decrease the CTRL fifo size */ + dlp_hsi_controller_pop(xfer_ctx); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + read_lock_irqsave(&xfer_ctx->lock, flags); + + /* Still have queued TX pdu ? */ + if (xfer_ctx->ctrl_len) { + mod_timer(&ch_ctx->hangup_timer, + jiffies + ch_ctx->hangup_delay); + } else { + del_timer(&ch_ctx->hangup_timer); + } + + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (dlp_ctx_is_empty(xfer_ctx)) { + wake_up(&ch_ctx->tx_empty_event); + mod_timer(&xfer_ctx->timer, jiffies + xfer_ctx->delay); + } else { + del_timer(&xfer_ctx->timer); + } + + read_lock_irqsave(&xfer_ctx->lock, flags); + pdu_available = (xfer_ctx->wait_len > 0); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (pdu_available) + dlp_pop_wait_push_ctrl(xfer_ctx, 1); + + /* Wake-up the TTY write whenever the TX wait FIFO is half empty, and + * not before, to prevent too many wakeups */ + wakeup = ((dlp_ctx_has_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT)) && + (xfer_ctx->wait_len <= xfer_ctx->wait_max / 2)); + if (wakeup) + dlp_ctx_clear_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT); + + if (wakeup) + dlp_tty_wakeup(ch_ctx); + + EPILOG(); +} + +/** + * dlp_tty_complete_rx - bottom-up flow for the RX side + * @pdu: a reference to the completed pdu + * + * A RX transfer has completed: push the data conveyed in the pdu to the TTY + * interface and signal any existing error. + */ +static void dlp_tty_complete_rx(struct hsi_msg *pdu) +{ + struct dlp_xfer_ctx *xfer_ctx = pdu->context; + struct dlp_tty_context *tty_ctx = xfer_ctx->channel->ch_data; + struct tty_struct *tty; + unsigned long flags; + + PROLOG(); + + tty = tty_port_tty_get(&tty_ctx->tty_prt); + + /* Dump the PDU : Do it before or after the actual_len update ? */ + dlp_pdu_dump(pdu, 1); + + /* Check and update the PDU len & status */ + dlp_pdu_update(tty_ctx->ch_ctx, pdu); + + /* Decrease the CTRL fifo size */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_hsi_controller_pop(xfer_ctx); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + +#ifdef CONFIG_HSI_DLP_TTY_STATS + xfer_ctx->tty_stats.data_sz += pdu->actual_len; + xfer_ctx->tty_stats.pdus_cnt++; + if (!xfer_ctx->ctrl_len) + xfer_ctx->tty_stats.overflow_cnt++; +#endif + + dlp_fifo_wait_push(xfer_ctx, pdu); + + dlp_tty_forward(tty, xfer_ctx); + + if (tty) + tty_kref_put(tty); + + EPILOG(); +} + +/** + * dlp_tty_tx_fifo_wait_recycle - recycle the whole content of the TX waiting FIFO + * @xfer_ctx: a reference to the TX context to consider + * + * This helper function is emptying a waiting TX FIFO and recycling all its + * pdus. + */ +static void dlp_tty_tx_fifo_wait_recycle(struct dlp_xfer_ctx *xfer_ctx) +{ + struct hsi_msg *pdu; + unsigned long flags; + + PROLOG(); + + dlp_ctx_clear_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT); + + write_lock_irqsave(&xfer_ctx->lock, flags); + + while ((pdu = dlp_fifo_wait_pop(xfer_ctx))) { + xfer_ctx->room -= dlp_pdu_room_in(pdu); + + /* check if pdu is active in dlp_tty_do_write */ + if (pdu->status != HSI_STATUS_PENDING) + dlp_pdu_delete(xfer_ctx, pdu); + else + pdu->break_frame = 0; + } + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG(); +} + +/** + * dlp_tty_rx_fifo_wait_recycle - recycle the whole content of the RX waiting FIFO + * @xfer_ctx: a reference to the RX context to consider + * + * This helper function is emptying a waiting RX FIFO and recycling all its + * pdus. + */ +static void dlp_tty_rx_fifo_wait_recycle(struct dlp_xfer_ctx *xfer_ctx) +{ + struct hsi_msg *pdu; + unsigned long flags; + unsigned int length = xfer_ctx->payload_len + DLP_TTY_HEADER_LENGTH; + + PROLOG(); + + dlp_ctx_clear_flag(xfer_ctx, + RX_TTY_FORWARDING_BIT | RX_TTY_REFORWARD_BIT); + + write_lock_irqsave(&xfer_ctx->lock, flags); + + while ((pdu = dlp_fifo_wait_pop(xfer_ctx))) { + /* Reset offset & length */ + dlp_pdu_reset(xfer_ctx, pdu, length); + + /* Recycle or Free the pdu */ + dlp_pdu_delete(xfer_ctx, pdu); + } + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + EPILOG(); +} + +/* + * TTY handling methods + */ + +/** + * dlp_tty_wait_until_ctx_sent - waits for all the TX FIFO to be empty + * @ch_ctx: a reference to the considered context + * @timeout: a timeout value expressed in jiffies + */ +inline void dlp_tty_wait_until_ctx_sent(struct dlp_channel *ch_ctx, int timeout) +{ + PROLOG(); + + wait_event_interruptible_timeout(ch_ctx->tx_empty_event, + dlp_ctx_is_empty(&ch_ctx->tx), + timeout); + + EPILOG(); +} + +/** + * dlp_tty_port_activate - callback to the TTY port activate function + * @port: a reference to the calling TTY port + * @tty: a reference to the calling TTY + * + * Return 0 on success or a negative error code on error. + * + * The TTY port activate is only called on the first port open. + */ +static int dlp_tty_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct dlp_channel *ch_ctx; + struct dlp_xfer_ctx *tx_ctx; + struct dlp_xfer_ctx *rx_ctx; + int ret; + + PROLOG(); + + /* Get the context reference stored in the TTY open() */ + ch_ctx = (struct dlp_channel *)tty->driver_data; + tx_ctx = &ch_ctx->tx; + rx_ctx = &ch_ctx->rx; + + /* Update the TX and RX HSI configuration */ + dlp_ctx_update_status(tx_ctx); + dlp_ctx_update_status(rx_ctx); + + /* Configure the DLP channel */ + // FIXME: To be removed (should be done in dlp_tty_ctx_create) + if (tty->count == 1) { + ret = dlp_ctrl_send_open_conn_cmd(ch_ctx); + if (ret) { + CRITICAL("dlp_ctrl_send_open_conn_cmd() failed !"); + goto out; + } + } + + /* Push all RX pdus */ + ret = dlp_pop_recycled_push_ctrl(rx_ctx); + if (ret == -EAGAIN) + mod_timer(&rx_ctx->timer, jiffies + rx_ctx->delay); + +out: + PDEBUG("tty port activated (ret: %d)", ret); + + EPILOG(); + return ret; +} + +/** + * dlp_tty_port_shutdown - callback to the TTY port shutdown function + * @port: a reference to the calling TTY port + * + * The TTY port shutdown is only called on the last port close. + */ +static void dlp_tty_port_shutdown(struct tty_port *port) +{ + struct dlp_channel *ch_ctx; + struct dlp_tty_context *tty_ctx; + struct dlp_xfer_ctx *tx_ctx; + struct dlp_xfer_ctx *rx_ctx; + + PROLOG(); + + tty_ctx = container_of(port, struct dlp_tty_context, tty_prt); + ch_ctx = tty_ctx->ch_ctx; + tx_ctx = &ch_ctx->tx; + rx_ctx = &ch_ctx->rx; + + /* we need hang_up timer alive to avoid long wait here */ + if (!(ch_ctx->hangup_reason & DLP_MODEM_HU_TIMEOUT)) + dlp_tty_wait_until_ctx_sent(ch_ctx, 0); + + del_timer_sync(&ch_ctx->hangup_timer); + + // FIXME: FTE + // hsi_flush(dlp_drv.client); + + /* RX */ + del_timer(&rx_ctx->timer); + dlp_tty_rx_fifo_wait_recycle(rx_ctx); + dlp_stop_rx(rx_ctx, ch_ctx); + + /* TX */ + del_timer(&tx_ctx->timer); + dlp_stop_tx(tx_ctx); + dlp_tty_tx_fifo_wait_recycle(tx_ctx); + + dlp_ctx_set_state(tx_ctx, IDLE); + + /* Close the HSI channel */ +#if 0 /* FIXME : Not supported by the modem */ + if (port->tty->count == 1) { + int ret; + ret = dlp_ctrl_send_cancel_conn_cmd(ch_ctx); + if (ret) { + CRITICAL("dlp_ctrl_send_cancel_conn_cmd() failed !"); + } + } +#endif + + PDEBUG("tty port shut down"); + EPILOG(); +} + +/** + * dlp_tty_open - callback to the TTY open function + * @tty: a reference to the calling TTY + * @filp: a reference to the calling file + * + * Return 0 on success or a negative error code on error. + * + * The HSI layer is only initialised during the first opening. + */ +static int dlp_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct dlp_channel *ch_ctx; + struct dlp_tty_context *tty_ctx; + int ret; + + PROLOG(); + + /* Check the modem readiness */ + if (! dlp_ctrl_modem_is_ready()) { + CRITICAL("Unale to open TTY (Modem NOT ready) !"); + ret = -EBUSY; + goto out; + } + + /* Get the context reference from the driver data if already opened */ + ch_ctx = (struct dlp_channel *)tty->driver_data; + + /* First open ? */ + if (!ch_ctx) { + ch_ctx = dlp_drv.channels[DLP_CHANNEL_TTY]; + tty->driver_data = ch_ctx; + } + + if (unlikely(!ch_ctx)) { + ret = -ENODEV; + CRITICAL("Cannot find TTY context (%d)", ret); + goto out; + } + + /* Open the TTY port (calls port->activate on first opening) */ + tty_ctx = ch_ctx->ch_data; + ret = tty_port_open(&tty_ctx->tty_prt, tty, filp); + if (ret) + CRITICAL("TTY open failed (%d)", ret); + + /* Set the TTY_NO_WRITE_SPLIT to transfer as much data as possible on + * the first write request. This shall not introduce denial of service + * as this flag will later adapt to the available TX buffer size. */ + tty->flags |= (1 << TTY_NO_WRITE_SPLIT); + +out: + EPILOG(); + return ret; +} + +/** + * dlp_tty_flush_tx_buffer - flushes the TX waiting FIFO + * @tty: a reference to the requesting TTY + */ +static void dlp_tty_flush_tx_buffer(struct tty_struct *tty) +{ + struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data; + struct dlp_xfer_ctx *xfer_ctx = &ch_ctx->tx; + + PROLOG(); + + dlp_tty_tx_fifo_wait_recycle(xfer_ctx); + + EPILOG(); +} + +/** + * dlp_tty_tx_stop - update the TX state machine after expiration of the TX active + * timeout further to a no outstanding TX transaction status + * @param: a hidden reference to the TX context to consider + * + * This helper function updates the TX state if it is currently active and + * inform the HSI pduwork and attached controller. + */ +void dlp_tty_tx_stop(unsigned long param) +{ + struct dlp_xfer_ctx *xfer_ctx = (struct dlp_xfer_ctx *)param; + + PROLOG(); + + dlp_stop_tx(xfer_ctx); + + EPILOG(); +} + +/** + * dlp_tty_hangup_timer_cb - timer function for tx timeout hangup request + * @param: a reference to the channel to consider + */ +static void dlp_tty_hangup_timer_cb(unsigned long int param) +{ + struct dlp_channel *ch_ctx = (struct dlp_channel *)param; + + PROLOG(); + + CRITICAL("Tx timeout"); + + ch_ctx->hangup_reason |= DLP_MODEM_HU_TIMEOUT; + queue_work(dlp_drv.tx_hangup_wq, &ch_ctx->hangup_queue); + + EPILOG(); +} + +/** + * dlp_tty_handle_tx_timeout - work queue function called from hangup timer + * @work: a reference to work queue element + * + * Required since port shutdown calls a mutex that might sleep + */ +static void dlp_tty_handle_tx_timeout(struct work_struct *work) +{ + struct dlp_channel *ch_ctx = container_of(work, struct dlp_channel, + hangup_queue); + struct dlp_tty_context *tty_ctx = ch_ctx->ch_data; + struct tty_struct *tty; + + PROLOG(); + + wake_up(&ch_ctx->tx_empty_event); + tty = tty_port_tty_get(&tty_ctx->tty_prt); + if (tty) { + tty_hangup(tty); + tty_kref_put(tty); + } + + EPILOG(); +} + +/** + * dlp_tty_hangup - callback to a TTY hangup request + * @tty: a reference to the requesting TTY + */ +static void dlp_tty_hangup(struct tty_struct *tty) +{ + struct dlp_tty_context *tty_ctx = + (((struct dlp_channel *)tty->driver_data))->ch_data; + + CRITICAL("tty hangup"); + + tty_port_hangup(&tty_ctx->tty_prt); +} + +/** + * dlp_tty_wait_until_sent - callback to a TTY wait until sent request + * @tty: a reference to the requesting TTY + * @timeout: a timeout value expressed in jiffies + */ +static void dlp_tty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data; + + PROLOG(); + + dlp_tty_wait_until_ctx_sent(ch_ctx, timeout); + + EPILOG(); +} + +/** + * dlp_tty_close - callback to the TTY close function + * @tty: a reference to the calling TTY + * @filp: a reference to the calling file + * + * The HSI layer is only released during the last closing. + */ +static void dlp_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data; + + PROLOG(); + + if (filp && ch_ctx) { + struct dlp_tty_context *tty_ctx = ch_ctx->ch_data; + tty_port_close(&tty_ctx->tty_prt, tty, filp); + } + + EPILOG(); +} + +/** + * dlp_tty_do_write - writes data coming from the TTY to the TX FIFO + * @xfer_ctx: a reference to the considered TX context + * @buf: the virtual address of the current input buffer (from TTY) + * @len: the remaining buffer size + * + * Returns the total size of what has been transferred. + * + * This is a recursive function, the core of the TTY write callback function. + */ +int dlp_tty_do_write(struct dlp_xfer_ctx *xfer_ctx, unsigned char *buf, + int len) +{ + struct hsi_msg *pdu; + int offset, avail, copied; + unsigned int updated_actual_len; + unsigned long flags; + + PROLOG(); + + offset = 0; + avail = 0; + copied = 0; + + if (!dlp_ctx_have_credits(xfer_ctx, xfer_ctx->channel)) { + CRITICAL("No credits available (%d)", xfer_ctx->seq_num); + goto out; + } + + read_lock_irqsave(&xfer_ctx->lock, flags); + pdu = dlp_fifo_tail(xfer_ctx, &xfer_ctx->wait_pdus); + if (pdu) { + offset = pdu->actual_len; + avail = xfer_ctx->payload_len - offset; + } + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (avail == 0) { + pdu = dlp_fifo_recycled_pop(xfer_ctx); + if (pdu) { + offset = 0; + + read_lock_irqsave(&xfer_ctx->lock, flags); + avail = xfer_ctx->payload_len; + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + dlp_fifo_wait_push(xfer_ctx, pdu); + } + } + + if (!pdu) { + goto out; + } + + pdu->status = HSI_STATUS_PENDING; + /* Do a start TX on new frames only and after having marked + * the current frame as pending, e.g. don't touch ! */ + if (offset == 0) { + dlp_hsi_start_tx(xfer_ctx); + } + else { + dlp_ctx_set_flag(xfer_ctx, TX_TTY_WRITE_PENDING_BIT); + } + + copied = min(avail, len); + updated_actual_len = pdu->actual_len + copied; + dlp_tty_pdu_set_length(pdu, updated_actual_len); + (void)memcpy(dlp_tty_pdu_data_ptr(pdu, offset), buf, copied); + + if (pdu->status != HSI_STATUS_ERROR) { /* still valid ? */ + pdu->actual_len = updated_actual_len; + pdu->status = HSI_STATUS_COMPLETED; + + write_lock_irqsave(&xfer_ctx->lock, flags); + + xfer_ctx->buffered += copied; + xfer_ctx->room -= copied; + + write_unlock_irqrestore(&xfer_ctx->lock, flags); + + read_lock_irqsave(&xfer_ctx->lock, flags); + avail = (xfer_ctx->ctrl_len < xfer_ctx->ctrl_max); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + if (avail) + dlp_pop_wait_push_ctrl(xfer_ctx, 0); + } else { + /* ERROR frames have already been popped from the wait FIFO */ + write_lock_irqsave(&xfer_ctx->lock, flags); + dlp_pdu_delete(xfer_ctx, pdu); + write_unlock_irqrestore(&xfer_ctx->lock, flags); + copied = 0; + } + +out: + EPILOG("%d", copied); + return copied; +} + +/** + * dlp_tty_write - writes data coming from the TTY to the TX FIFO + * @tty: a reference to the calling TTY + * @buf: the virtual address of the current input buffer (from TTY) + * @len: the TTY buffer size + * + * Returns the total size of what has been transferred in the TX FIFO + * + * This is the TTY write callback function. + */ +static int dlp_tty_write(struct tty_struct *tty, const unsigned char *buf, + int len) +{ + struct dlp_xfer_ctx *xfer_ctx = + &((struct dlp_channel *)tty->driver_data)->tx; + int already_copied, copied; + unsigned char *ptr; + unsigned long flags; + + PROLOG("seq_num: %d, len: %d", xfer_ctx->seq_num, len); + + read_lock_irqsave(&xfer_ctx->lock, flags); + if (xfer_ctx->room >= len) + tty->flags |= (1 << TTY_NO_WRITE_SPLIT); + else + tty->flags &= ~(1 << TTY_NO_WRITE_SPLIT); + read_unlock_irqrestore(&xfer_ctx->lock, flags); + + already_copied = 0; + while (len > 0) { + ptr = (unsigned char *)(buf + already_copied); + copied = dlp_tty_do_write(xfer_ctx, ptr, len); + if (copied == 0) + break; + already_copied += copied; + len -= copied; + } + + EPILOG("%d", already_copied); + return already_copied; +} + +/** + * dlp_tty_write_room - returns the available buffer size on the TX FIFO + * @tty: a reference to the calling TTY + * + * Returns the total available size in the TX wait FIFO. + */ +static int dlp_tty_write_room(struct tty_struct *tty) +{ + struct dlp_xfer_ctx *ch_ctx = + &((struct dlp_channel *)tty->driver_data)->tx; + unsigned int room; + unsigned long flags; + + PROLOG(); + + read_lock_irqsave(&ch_ctx->lock, flags); + room = ch_ctx->room; + read_unlock_irqrestore(&ch_ctx->lock, flags); + + EPILOG("%d", room); + return room; +} + +/** + * dlp_tty_chars_in_buffer - returns the size of the data hold in the TX FIFO + * @tty: a reference to the calling TTY + * + * Returns the total size of data hold in the TX wait FIFO. It does not take + * into account the data which has already been passed to the HSI controller + * in both in software and hardware FIFO. + */ +static int dlp_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct dlp_xfer_ctx *ch_ctx = + &((struct dlp_channel *)tty->driver_data)->tx; + unsigned int buffered; + unsigned long flags; + + PROLOG(); + + read_lock_irqsave(&ch_ctx->lock, flags); + buffered = ch_ctx->buffered; + read_unlock_irqrestore(&ch_ctx->lock, flags); + + EPILOG("%d", buffered); + return buffered; +} + +/** + * dlp_tty_ioctl - manages the IOCTL read and write requests + * @tty: a reference to the calling TTY + * @cmd: the IOCTL command + * @arg: the I/O argument to pass or retrieve data + * + * Returns 0 upon normal completion or the error code in case of an error. + */ +static int dlp_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct dlp_channel *ch_ctx = (struct dlp_channel *)tty->driver_data; + struct work_struct *increase_pool = NULL; + unsigned int data; +#ifdef CONFIG_HSI_DLP_TTY_STATS + struct hsi_dlp_stats stats; +#endif + unsigned long flags; + int ret; + + PROLOG(); + + switch (cmd) { + case HSI_DLP_RESET_TX: + dlp_tty_tx_fifo_wait_recycle(&ch_ctx->tx); + break; + + case HSI_DLP_RESET_RX: + dlp_tty_rx_fifo_wait_recycle(&ch_ctx->rx); + break; + + case HSI_DLP_GET_TX_STATE: + data = dlp_ctx_get_state(&ch_ctx->tx); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_GET_RX_STATE: + data = dlp_ctx_get_state(&ch_ctx->rx); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_WAIT_MAX: + if (arg > 0) { + write_lock_irqsave(&ch_ctx->tx.lock, flags); + if (arg > ch_ctx->tx.wait_max) + increase_pool = &ch_ctx->tx.increase_pool; + ch_ctx->tx.wait_max = arg; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + } else { + dev_dbg(&dlp_drv.client->device, + "Invalid TX wait FIFO size %li\n", arg); + return -EINVAL; + } + break; + + case HSI_DLP_GET_TX_WAIT_MAX: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.wait_max; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_RX_WAIT_MAX: + if (arg > 0) { + write_lock_irqsave(&ch_ctx->rx.lock, flags); + if (arg > ch_ctx->rx.ctrl_max) + increase_pool = &ch_ctx->rx.increase_pool; + ch_ctx->rx.wait_max = arg; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + } else { + dev_dbg(&dlp_drv.client->device, + "Invalid RX wait FIFO size %li\n", arg); + return -EINVAL; + } + break; + + case HSI_DLP_GET_RX_WAIT_MAX: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + data = ch_ctx->rx.wait_max; + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_CTRL_MAX: + if (arg > 0) { + write_lock_irqsave(&ch_ctx->tx.lock, flags); + if (arg > ch_ctx->tx.ctrl_max) + increase_pool = &ch_ctx->tx.increase_pool; + ch_ctx->tx.ctrl_max = arg; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + } else { + dev_dbg(&dlp_drv.client->device, + "Invalid TX controller FIFO size %li\n", arg); + return -EINVAL; + } + break; + + case HSI_DLP_GET_TX_CTRL_MAX: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.ctrl_max; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_RX_CTRL_MAX: + if (arg > 0) { + write_lock_irqsave(&ch_ctx->rx.lock, flags); + if (arg > ch_ctx->rx.ctrl_max) + increase_pool = &ch_ctx->rx.increase_pool; + ch_ctx->rx.ctrl_max = arg; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + } else { + dev_dbg(&dlp_drv.client->device, + "Invalid RX controller FIFO size %li\n", arg); + return -EINVAL; + } + break; + + case HSI_DLP_GET_RX_CTRL_MAX: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + data = ch_ctx->rx.ctrl_max; + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_DELAY: + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.delay = from_usecs(arg); + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + + case HSI_DLP_GET_TX_DELAY: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = jiffies_to_usecs(ch_ctx->tx.delay); + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_RX_DELAY: + write_lock_irqsave(&ch_ctx->rx.lock, flags); + ch_ctx->rx.delay = from_usecs(arg); + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + break; + + case HSI_DLP_GET_RX_DELAY: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + data = jiffies_to_usecs(ch_ctx->rx.delay); + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_FLOW: + switch (arg) { + case HSI_FLOW_SYNC: + case HSI_FLOW_PIPE: + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.config.flow = arg; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + default: + return -EINVAL; + } + break; + + case HSI_DLP_GET_TX_FLOW: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.config.flow; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_RX_FLOW: + switch (arg) { + case HSI_FLOW_SYNC: + case HSI_FLOW_PIPE: + write_lock_irqsave(&ch_ctx->rx.lock, flags); + dlp_drv.client->rx_cfg.flow = arg; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + break; + default: + return -EINVAL; + } + break; + + case HSI_DLP_GET_RX_FLOW: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + data = ch_ctx->rx.config.flow; + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_MODE: + switch (arg) { + case HSI_MODE_STREAM: + case HSI_MODE_FRAME: + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.config.mode = arg; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + default: + return -EINVAL; + } + break; + + case HSI_DLP_GET_TX_MODE: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.config.mode; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_RX_MODE: + switch (arg) { + case HSI_MODE_STREAM: + case HSI_MODE_FRAME: + write_lock_irqsave(&ch_ctx->rx.lock, flags); + ch_ctx->rx.config.mode = arg; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + break; + default: + return -EINVAL; + } + break; + + case HSI_DLP_GET_RX_MODE: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + data = ch_ctx->rx.config.mode; + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_PDU_LEN: + if ((arg <= DLP_TTY_HEADER_LENGTH) || (arg > ch_ctx->pdu_size)) + return -EINVAL; + + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.payload_len = + ((arg + 3) / 4) * 4 - DLP_TTY_HEADER_LENGTH; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + + case HSI_DLP_GET_TX_PDU_LEN: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.payload_len + DLP_TTY_HEADER_LENGTH; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_RX_PDU_LEN: + if ((arg <= DLP_TTY_HEADER_LENGTH) || (arg > ch_ctx->pdu_size)) + return -EINVAL; + write_lock_irqsave(&ch_ctx->rx.lock, flags); + ch_ctx->rx.payload_len = + ((arg + 3) / 4) * 4 - DLP_TTY_HEADER_LENGTH; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + break; + + case HSI_DLP_GET_RX_PDU_LEN: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + data = ch_ctx->rx.payload_len + DLP_TTY_HEADER_LENGTH; + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_ARB_MODE: + switch (arg) { + case HSI_ARB_RR: + case HSI_ARB_PRIO: + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.config.arb_mode = arg; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + default: + return -EINVAL; + } + break; + + case HSI_DLP_GET_TX_ARB_MODE: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.config.arb_mode; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_SET_TX_FREQUENCY: + if (arg == 0) + return -EINVAL; + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.config.speed = arg; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + + case HSI_DLP_GET_TX_FREQUENCY: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + data = ch_ctx->tx.config.speed; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return put_user(data, (unsigned int __user *)arg); + break; + +#ifdef CONFIG_HSI_DLP_TTY_STATS + case HSI_DLP_RESET_TX_STATS: + write_lock_irqsave(&ch_ctx->tx.lock, flags); + ch_ctx->tx.tty_stats.data_sz = 0; + ch_ctx->tx.tty_stats.pdus_cnt = 0; + ch_ctx->tx.tty_stats.overflow_cnt = 0; + write_unlock_irqrestore(&ch_ctx->tx.lock, flags); + break; + + case HSI_DLP_GET_TX_STATS: + read_lock_irqsave(&ch_ctx->tx.lock, flags); + stats.data_sz = ch_ctx->tx.tty_stats.data_sz; + stats.pdus_cnt = ch_ctx->tx.tty_stats.pdus_cnt; + stats.overflow_cnt = ch_ctx->tx.tty_stats.overflow_cnt; + read_unlock_irqrestore(&ch_ctx->tx.lock, flags); + return copy_to_user((void __user *)arg, &stats, sizeof(stats)); + break; + + case HSI_DLP_RESET_RX_STATS: + write_lock_irqsave(&ch_ctx->rx.lock, flags); + ch_ctx->rx.tty_stats.data_sz = 0; + ch_ctx->rx.tty_stats.pdus_cnt = 0; + ch_ctx->rx.tty_stats.overflow_cnt = 0; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + break; + + case HSI_DLP_GET_RX_STATS: + read_lock_irqsave(&ch_ctx->rx.lock, flags); + stats.data_sz = ch_ctx->rx.tty_stats.data_sz; + stats.pdus_cnt = ch_ctx->rx.tty_stats.pdus_cnt; + stats.overflow_cnt = ch_ctx->rx.tty_stats.overflow_cnt; + read_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return copy_to_user((void __user *)arg, &stats, sizeof(stats)); + break; +#endif + + case HSI_DLP_MODEM_RESET: + WARNING("Modem reset requested"); + dlp_ctrl_modem_reset(ch_ctx); + break; + + case HSI_DLP_MODEM_STATE: + data = !(dlp_drv.reset_ignore); + return put_user(data, (unsigned int __user *)arg); + break; + + case HSI_DLP_GET_HANGUP_REASON: + ret = put_user(ch_ctx->hangup_reason, + (unsigned int __user *)arg); + write_lock_irqsave(&ch_ctx->rx.lock, flags); + ch_ctx->hangup_reason = 0; + write_unlock_irqrestore(&ch_ctx->rx.lock, flags); + return ret; + break; + + default: + return -ENOIOCTLCMD; + } + + if (increase_pool) + (void)queue_work(dlp_drv.recycle_wq, increase_pool); + + EPILOG(); + return 0; +} + +/* + * Protocol driver handling routines + */ + +/* + * dlp_termios_init - default termios initialisation + */ +static const struct ktermios dlp_termios_init = { + .c_iflag = 0, + .c_oflag = 0, + .c_cflag = B115200 | CS8, + .c_lflag = 0, + .c_cc = INIT_C_CC, + .c_ispeed = 0, + .c_ospeed = 0 +}; + +/* + * dlp_driver_tty_ops - table of supported TTY operations + */ +static const struct tty_operations dlp_driver_tty_ops = { + .open = dlp_tty_open, + .close = dlp_tty_close, + .write = dlp_tty_write, + .write_room = dlp_tty_write_room, + .chars_in_buffer = dlp_tty_chars_in_buffer, + .ioctl = dlp_tty_ioctl, + .hangup = dlp_tty_hangup, + .wait_until_sent = dlp_tty_wait_until_sent, + .unthrottle = dlp_tty_rx_forward_resume, + .flush_buffer = dlp_tty_flush_tx_buffer, +}; + +/* + * dlp_port_tty_ops - table of supported TTY port operations + */ +static const struct tty_port_operations dlp_port_tty_ops = { + .activate = dlp_tty_port_activate, + .shutdown = dlp_tty_port_shutdown, +}; + +/**************************************************************************** + * + * Exported functions + * + ***************************************************************************/ + +struct dlp_channel *dlp_tty_ctx_create(unsigned int index, struct device *dev) +{ + struct hsi_client *client = to_hsi_client(dev); + struct tty_driver *new_drv; + struct dlp_channel *ch_ctx; + struct dlp_tty_context *tty_ctx; + int ret; + + PROLOG(); + + ch_ctx = kzalloc(sizeof(struct dlp_channel), GFP_KERNEL); + if (!ch_ctx) { + CRITICAL("Unable to allocate memory (ch_ctx)"); + goto out; + } + + /* Allocate the context private data */ + tty_ctx = kzalloc(sizeof(struct dlp_tty_context), GFP_KERNEL); + if (!tty_ctx) { + CRITICAL("Unable to allocate memory (tty_ctx)"); + goto free_ch; + } + + /* Allocate & configure the TTY driver */ + new_drv = alloc_tty_driver(1); + if (unlikely(!new_drv)) { + CRITICAL("alloc_tty_driver() failed !"); + goto free_ctx; + } + + /* Configure the TTY */ + new_drv->magic = TTY_DRIVER_MAGIC; + new_drv->owner = THIS_MODULE; + new_drv->driver_name = DRVNAME; + new_drv->name = TTYNAME; + new_drv->minor_start = 0; + new_drv->num = 1; + new_drv->type = TTY_DRIVER_TYPE_SERIAL; + new_drv->subtype = SERIAL_TYPE_NORMAL; + new_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + new_drv->init_termios = dlp_termios_init; + + tty_set_operations(new_drv, &dlp_driver_tty_ops); + + /* Register the TTY driver */ + ret = tty_register_driver(new_drv); + if (unlikely(ret)) { + CRITICAL("tty_register_driver() failed (%d)", ret); + goto free_drv; + } + + /* Save params */ + ch_ctx->ch_data = tty_ctx; + ch_ctx->hsi_channel = index; + ch_ctx->credits = 0; + ch_ctx->pdu_size = DLP_TTY_PDU_LENGTH; + ch_ctx->rx.config = client->rx_cfg; + ch_ctx->tx.config = client->tx_cfg; + + dlp_drv.reset_ignore = 1; + + spin_lock_init(&ch_ctx->lock); + init_timer(&ch_ctx->hangup_timer); + init_waitqueue_head(&ch_ctx->tx_empty_event); + INIT_WORK(&ch_ctx->hangup_queue, dlp_tty_handle_tx_timeout); + + ch_ctx->hangup_delay = from_usecs(DLP_HANGUP_DELAY); + ch_ctx->hangup_timer.function = dlp_tty_hangup_timer_cb; + ch_ctx->hangup_timer.data = (unsigned long int)ch_ctx; + + ch_ctx->modem_coredump_cb = dlp_tty_mdm_coredump_cb; + ch_ctx->modem_reset_cb = dlp_tty_mdm_reset_cb; + + /* TX & RX contexts */ + dlp_xfer_ctx_init(ch_ctx, &ch_ctx->tx, + DLP_HSI_TX_DELAY, + DLP_HSI_TX_WAIT_FIFO, DLP_HSI_TX_CTRL_FIFO, + dlp_tty_complete_tx, HSI_MSG_WRITE); + + dlp_xfer_ctx_init(ch_ctx, &ch_ctx->rx, + DLP_HSI_RX_DELAY, + DLP_HSI_RX_WAIT_FIFO, DLP_HSI_RX_CTRL_FIFO, + dlp_tty_complete_rx, HSI_MSG_READ); + + ch_ctx->tx.timer.function = dlp_tty_tx_stop; + ch_ctx->rx.timer.function = dlp_tty_rx_forward_retry; + + /* Register the TTY device (port) */ + tty_port_init(&(tty_ctx->tty_prt)); + tty_ctx->tty_prt.ops = &dlp_port_tty_ops; + + if (unlikely(!tty_register_device(new_drv, 0, dev))) { + CRITICAL("tty_register_device() failed (%d)!", ret); + goto unreg_drv; + } + + tty_ctx->ch_ctx = ch_ctx; + tty_ctx->tty_drv = new_drv; + + /* Allocate RX & TX FIFOs in background */ + queue_work(dlp_drv.recycle_wq, &ch_ctx->tx.increase_pool); + queue_work(dlp_drv.recycle_wq, &ch_ctx->rx.increase_pool); + + EPILOG(); + return ch_ctx; + +unreg_drv: + tty_unregister_driver(new_drv); + +free_drv: + put_tty_driver(new_drv); + +free_ctx: + kfree(tty_ctx); + +free_ch: + kfree(ch_ctx); + +out: + EPILOG("Failed"); + return NULL; +} + +int dlp_tty_ctx_delete(struct dlp_channel *ch_ctx) +{ + int ret = 0; + struct dlp_tty_context *tty_ctx = ch_ctx->ch_data; + + PROLOG(); + + del_timer(&ch_ctx->hangup_timer); + + /* Unregister device */ + tty_unregister_device(tty_ctx->tty_drv, 0); + + /* Unregister driver */ + tty_unregister_driver(tty_ctx->tty_drv); + + /* Free */ + put_tty_driver(tty_ctx->tty_drv); + tty_ctx->tty_drv = NULL; + + /* Delete the xfers context */ + dlp_xfer_ctx_clear(&ch_ctx->tx); + dlp_xfer_ctx_clear(&ch_ctx->rx); + + /* Free the tty_ctx */ + kfree(tty_ctx); + + /* Free the ch_ctx */ + kfree(ch_ctx); + + EPILOG(); + return ret; +} diff --git a/drivers/hsi/controllers/intel_mid_hsi.c b/drivers/hsi/controllers/intel_mid_hsi.c index fe14e69..42fe0af 100644 --- a/drivers/hsi/controllers/intel_mid_hsi.c +++ b/drivers/hsi/controllers/intel_mid_hsi.c @@ -35,7 +35,11 @@ /* Set the following to prevent ACWAKE toggling (for debugging). This also * disbales power management */ +#ifdef CONFIG_HSI_DLP +#define PREVENT_ACWAKE_TOGGLING +#else #undef PREVENT_ACWAKE_TOGGLING +#endif /* Set the following to allow software workaround of the DMA link listing */ #define USE_SOFWARE_WORKAROUND_FOR_DMA_LLI @@ -3466,3 +3470,4 @@ MODULE_ALIAS("pci:intel_hsi"); MODULE_AUTHOR("Olivier Stoltz Douchet "); MODULE_DESCRIPTION("Intel mid HSI Controller Driver"); MODULE_LICENSE("GPL v2"); + diff --git a/drivers/hsi/dlp_debug.h b/drivers/hsi/dlp_debug.h new file mode 100644 index 0000000..ea1e700 --- /dev/null +++ b/drivers/hsi/dlp_debug.h @@ -0,0 +1,172 @@ +/* + * hsi_dlp_debug.h + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (LTE)). This driver is implementing a 5-channel HSI + * protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Olivier Stoltz Douchet + * Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef _HSI_DLP_DEBUG_H_ +#define _HSI_DLP_DEBUG_H_ + +/****************************************************************************** + * To enable debug: + * + * - define the CONFIG_DEBUG debug macro (kernel compile) + * + * - define a DEBUG_VAR : Variable to dynamically filter tag/level + * (typically a module parameter) + * + * - define a DEBUG_TAG : Tag to debug (typically 1 by .c file) + * + * - Activate the debug by: + * echo 0xTAGLEVEL > /sys/module/dlp_lte/parameters/debug + * + * LEVEL is defined on 12 bits: + * 0x000 => nothing, except CRITICAL, WARNING which are always displayed + * 0x001 => usefull light traces (PRINT, PTRACE) + * 0x002 => prolog & epilog (functions in/out) + * 0x004 => Dump TX PDUs + * 0x008 => Dump RX PDUs + * + * 0xFFF => deep debug (everything) + * + * LEVEL can be mixed together, for example: + * 0xFFF => Debug everything (All traces) + * 0x3 => prolog/epilog + traces + * + * TAG: + * 0x1 => COMM (to debug the common related part) + * 0x2 => CTRL (to debug the ctrl related part) + * 0x4 => TTY (to debug the tty related part) + * 0x8 => NET (to debug the netif related part) + * + * Exp: to debug everything (all TAGs & all LEVELs) : + * echo 0xFFFF > /sys/module/dlp_lte/parameters/debug + * + * Exp: to debug the TTY functions entry/exit: + * echo 0x4002 > /sys/module/dlp_lte/parameters/debug + * + * Exp: to deactivate the debug : + * echo 0x0 > /sys/module/dlp_lte/parameters/debug + * + *****************************************************************************/ +#include + +#define DEBUG_NONE 0x000 +#define DEBUG_TRACES 0x001 +#define DEBUG_PROLOG 0x002 +#define DEBUG_TX_PDU 0x004 +#define DEBUG_RX_PDU 0x008 +#define DEBUG_ALL 0xFFF + +#define DEBUG_LEVEL_SZ 12 +#define DEBUG_CURR_TAG (DEBUG_TAG << DEBUG_LEVEL_SZ) + +#ifdef DEBUG + #define PRINT(fmt, args...) printk (fmt, ## args) +#else + #define PRINT(fmt, args...) /* nothing, its a placeholder */ +#endif + +#define CRITICAL(fmt, args...) printk("! %s(" fmt ")\n", __func__, ## args) +#define WARNING(fmt, args...) printk("? %s(" fmt ")\n", __func__, ## args) + +#define PTRACE(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_TRACES | DEBUG_CURR_TAG)) \ + PRINT (" %s(" fmt ")\n", __func__, ## args); \ + while (0) + +#define PTRACE_NO_FUNC(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_TRACES | DEBUG_CURR_TAG)) \ + PRINT (fmt, ## args); \ + while (0) + +#define PRINT_TX(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_TX_PDU | DEBUG_CURR_TAG)) \ + PRINT (fmt, ## args); \ + while (0) + +#define PRINT_RX(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_RX_PDU | DEBUG_CURR_TAG)) \ + PRINT (fmt, ## args); \ + while (0) + +#define PROLOG(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_PROLOG | DEBUG_CURR_TAG)) \ + PRINT ("> %s(" fmt ")\n", __func__, ## args); \ + while (0) + +#define EPILOG(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_PROLOG | DEBUG_CURR_TAG)) \ + PRINT ("< %s(" fmt ")\n", __func__, ## args); \ + while (0) + +#define PDEBUG(fmt, args...) \ + do \ + if (DEBUG_VAR & (DEBUG_ALL | DEBUG_CURR_TAG)) \ + PRINT (" %s(" fmt ")\n", __func__, ## args); \ + while (0) + + + +#define HSI_MSG_STATUS_TO_STR(status) \ + (status == HSI_STATUS_COMPLETED ? "COMPLETED ": \ + (status == HSI_STATUS_PENDING ? "PENDING ": \ + (status == HSI_STATUS_PROCEEDING) ? "PROCEEDING": \ + (status == HSI_STATUS_QUEUED) ? "QUEUED ": \ + (status == HSI_STATUS_ERROR) ? "ERROR " : "UNKNOWN ")) + + +/* + * + * + */ +void dlp_dbg_dump_data_as_word(unsigned int *data, + int nb_words, + int words_per_line, + int is_corrupted, + int is_rx); + +void dlp_dbg_dump_data_as_byte(unsigned char *data, + int nb_bytes, + int bytes_per_line, + int is_corrupted, + int is_rx); + +void dlp_dbg_dump_pdu(struct hsi_msg *pdu, + int items_per_line, + int items_to_dump, + int dump_as_words); + +#endif /* _HSI_DLP_DEBUG_H_ */ + diff --git a/include/linux/hsi/hsi_dlp.h b/include/linux/hsi/hsi_dlp.h new file mode 100644 index 0000000..48ca169 --- /dev/null +++ b/include/linux/hsi/hsi_dlp.h @@ -0,0 +1,270 @@ +/* + * hsi_dlp.h + * + * Intel Mobile Communication modem protocol driver for DLP + * (Data Link Protocl (LTE)). + * This driver is implementing a 5-channel HSI protocol consisting of: + * - An internal communication control channel; + * - A multiplexed channel exporting a TTY interface; + * - Three dedicated high speed channels exporting each a network interface. + * All channels are using fixed-length pdus, although of different sizes. + * + * Copyright (C) 2010-2011 Intel Corporation. All rights reserved. + * + * Contact: Olivier Stoltz Douchet + * Faouaz Tenoutit + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef _HSI_DLP_H +#define _HSI_DLP_H + +#include + +/* reasons for hanging up */ +enum { + DLP_MODEM_HU_TIMEOUT = 1, + DLP_MODEM_HU_RESET = 2, + DLP_MODEM_HU_COREDUMP = 4, +}; + +/** + * struct hsi_dlp_stats - statistics related to the TX and RX side + * @data_sz: total size of actual transferred data + * @pdus_cnt: total number of transferred puds + * @overflow_cnt: total number of transfer stalls due to FIFO full + */ +struct hsi_dlp_stats { + unsigned long long data_sz; + unsigned int pdus_cnt; + unsigned int overflow_cnt; +}; + +#define HSI_DLP_MAGIC 0x77 + +/* + * HSI_DLP_RESET_TX - reset the TX state machine (flushes it) + */ +#define HSI_DLP_RESET_TX _IO(HSI_DLP_MAGIC, 0) + +/* + * HSI_DLP_RESET_RX - reset the RX state machine (flushes it) + */ +#define HSI_DLP_RESET_RX _IO(HSI_DLP_MAGIC, 1) + +/* + * HSI_DLP_GET_TX_STATE - get the current state of the TX state machine + */ +#define HSI_DLP_GET_TX_STATE _IOR(HSI_DLP_MAGIC, 2, unsigned int) + +/* + * HSI_DLP_GET_RX_STATE - get the current state of the RX state machine + */ +#define HSI_DLP_GET_RX_STATE _IOR(HSI_DLP_MAGIC, 3, unsigned int) + +/* + * HSI_DLP_MODEM_RESET - reset the modem (solicited reset) + * Shared with SPI + */ +#define HSI_DLP_MODEM_RESET _IO(HSI_DLP_MAGIC, 4) + +/* + * HSI_DLP_MODEM_STATE - return 1 if first transmission completed + * Shared with SPI + */ +#define HSI_DLP_MODEM_STATE _IOR(HSI_DLP_MAGIC, 5, int) + +/* + * HSI_DLP_GET_HANGUP_REASON - return reason for latest hangup + * Shared with SPI + */ +#define HSI_DLP_GET_HANGUP_REASON _IOR(HSI_DLP_MAGIC, 6, int) + +/* + * HSI_DLP_SET_TX_WAIT_MAX - set the maximal size of the TX waiting FIFO + */ +#define HSI_DLP_SET_TX_WAIT_MAX _IOW(HSI_DLP_MAGIC, 8, unsigned int) + +/* + * HSI_DLP_GET_TX_WAIT_MAX - get the maximal size of the TX waiting FIFO + */ +#define HSI_DLP_GET_TX_WAIT_MAX _IOR(HSI_DLP_MAGIC, 8, unsigned int) + +/* + * HSI_DLP_SET_RX_WAIT_MAX - set the maximal size of the RX waiting FIFO + */ +#define HSI_DLP_SET_RX_WAIT_MAX _IOW(HSI_DLP_MAGIC, 9, unsigned int) + +/* + * HSI_DLP_GET_RX_WAIT_MAX - get the maximal size of the RX waiting FIFO + */ +#define HSI_DLP_GET_RX_WAIT_MAX _IOR(HSI_DLP_MAGIC, 9, unsigned int) + +/* + * HSI_DLP_SET_TX_CTRL_MAX - set the maximal size of the TX controller FIFO + */ +#define HSI_DLP_SET_TX_CTRL_MAX _IOW(HSI_DLP_MAGIC, 10, unsigned int) + +/* + * HSI_DLP_GET_TX_CTRL_MAX - get the maximal size of the TX controller FIFO + */ +#define HSI_DLP_GET_TX_CTRL_MAX _IOR(HSI_DLP_MAGIC, 10, unsigned int) + +/* + * HSI_DLP_SET_RX_CTRL_MAX - set the maximal size of the RX controller FIFO + */ +#define HSI_DLP_SET_RX_CTRL_MAX _IOW(HSI_DLP_MAGIC, 11, unsigned int) + +/* + * HSI_DLP_GET_RX_CTRL_MAX - get the maximal size of the RX controller FIFO + */ +#define HSI_DLP_GET_RX_CTRL_MAX _IOR(HSI_DLP_MAGIC, 11, unsigned int) + +/* + * HSI_DLP_SET_TX_DELAY - set the TX delay in us + */ +#define HSI_DLP_SET_TX_DELAY _IOW(HSI_DLP_MAGIC, 12, unsigned int) + +/* + * HSI_DLP_GET_TX_DELAY - get the TX delay in us + */ +#define HSI_DLP_GET_TX_DELAY _IOR(HSI_DLP_MAGIC, 12, unsigned int) + +/* + * HSI_DLP_SET_RX_DELAY - set the RX delay in us + */ +#define HSI_DLP_SET_RX_DELAY _IOW(HSI_DLP_MAGIC, 13, unsigned int) + +/* + * HSI_DLP_GET_RX_DELAY - get the RX delay in us + */ +#define HSI_DLP_GET_RX_DELAY _IOR(HSI_DLP_MAGIC, 13, unsigned int) + +/* + * HSI_DLP_SET_TX_FLOW - set the TX flow type (PIPE, SYNC) + */ +#define HSI_DLP_SET_TX_FLOW _IOW(HSI_DLP_MAGIC, 16, unsigned int) + +/* + * HSI_DLP_GET_TX_FLOW - get the TX flow type (PIPE, SYNC) + */ +#define HSI_DLP_GET_TX_FLOW _IOR(HSI_DLP_MAGIC, 16, unsigned int) + +/* + * HSI_DLP_SET_RX_FLOW - set the RX flow type (PIPE, SYNC) + */ +#define HSI_DLP_SET_RX_FLOW _IOW(HSI_DLP_MAGIC, 17, unsigned int) + +/* + * HSI_DLP_GET_RX_FLOW - get the RX flow type (PIPE, SYNC) + */ +#define HSI_DLP_GET_RX_FLOW _IOR(HSI_DLP_MAGIC, 17, unsigned int) + +/* + * HSI_DLP_SET_TX_MODE - set the TX mode type (FRAME, STREAM) + */ +#define HSI_DLP_SET_TX_MODE _IOW(HSI_DLP_MAGIC, 18, unsigned int) + +/* + * HSI_DLP_GET_TX_MODE - get the TX mode type (FRAME, STREAM) + */ +#define HSI_DLP_GET_TX_MODE _IOR(HSI_DLP_MAGIC, 18, unsigned int) + +/* + * HSI_DLP_SET_RX_MODE - set the RX mode type (FRAME, STREAM) + */ +#define HSI_DLP_SET_RX_MODE _IOW(HSI_DLP_MAGIC, 19, unsigned int) + +/* + * HSI_DLP_GET_RX_MODE - get the RX mode type (FRAME, STREAM) + */ +#define HSI_DLP_GET_RX_MODE _IOR(HSI_DLP_MAGIC, 19, unsigned int) + +/* + * HSI_DLP_SET_TX_PDU_LEN - set the FFL TX frame length + */ +#define HSI_DLP_SET_TX_PDU_LEN _IOW(HSI_DLP_MAGIC, 24, unsigned int) + +/* + * HSI_DLP_GET_TX_PDU_LEN - get the FFL TX frame length + */ +#define HSI_DLP_GET_TX_PDU_LEN _IOR(HSI_DLP_MAGIC, 24, unsigned int) + +/* + * HSI_DLP_SET_RX_PDU_LEN - set the FFL RX frame length + */ +#define HSI_DLP_SET_RX_PDU_LEN _IOW(HSI_DLP_MAGIC, 25, unsigned int) + +/* + * HSI_DLP_GET_RX_PDU_LEN - get the FFL RX frame length + */ +#define HSI_DLP_GET_RX_PDU_LEN _IOR(HSI_DLP_MAGIC, 25, unsigned int) + +/* + * HSI_DLP_SET_TX_ARB_MODE - set the FFL TX arbitration (RR ou priority) + */ +#define HSI_DLP_SET_TX_ARB_MODE _IOW(HSI_DLP_MAGIC, 28, unsigned int) + +/* + * HSI_DLP_GET_TX_ARB_MODE - get the FFL TX arbitration (RR or priority) + */ +#define HSI_DLP_GET_TX_ARB_MODE _IOR(HSI_DLP_MAGIC, 28, unsigned int) + +/* + * HSI_DLP_SET_TX_FREQUENCY - set the maximum FFL TX frequency (in kbit/s) + */ +#define HSI_DLP_SET_TX_FREQUENCY _IOW(HSI_DLP_MAGIC, 30, unsigned int) + +/* + * HSI_DLP_GET_TX_FREQUENCY - get the maximum FFL TX frequency (in kbit/s) + */ +#define HSI_DLP_GET_TX_FREQUENCY _IOR(HSI_DLP_MAGIC, 30, unsigned int) + +/* + * HSI_DLP_RESET_TX_STATS - reset the TX statistics + */ +#define HSI_DLP_RESET_TX_STATS _IO(HSI_DLP_MAGIC, 32) + +/* + * HSI_DLP_GET_TX_STATS - get the TX statistics + */ +#define HSI_DLP_GET_TX_STATS _IOR(HSI_DLP_MAGIC, 32, \ + struct hsi_dlp_stats) + +/* + * HSI_DLP_RESET_RX_STATS - reset the RX statistics + */ +#define HSI_DLP_RESET_RX_STATS _IO(HSI_DLP_MAGIC, 33) + +/* + * HSI_DLP_GET_RX_STATS - get the RX statistics + */ +#define HSI_DLP_GET_RX_STATS _IOR(HSI_DLP_MAGIC, 33, \ + struct hsi_dlp_stats) + +/* + * HSI_DLP_NET_RESET_RX_STATS - reset the network interface RX statistics + */ +#define HSI_DLP_NET_RESET_RX_STATS _IOW(HSI_DLP_MAGIC, 40, unsigned int) + +/* + * HSI_DLP_NET_RESET_TX_STATS - reset the network interface TX statistics + */ +#define HSI_DLP_NET_RESET_TX_STATS _IOW(HSI_DLP_MAGIC, 41, unsigned int) + + + +#endif /* _HSI_DLP_H */ + -- 2.7.4