diff options
Diffstat (limited to 'drivers/misc/modem_if/modem_link_device_dpram.c')
-rw-r--r-- | drivers/misc/modem_if/modem_link_device_dpram.c | 3184 |
1 files changed, 2115 insertions, 1069 deletions
diff --git a/drivers/misc/modem_if/modem_link_device_dpram.c b/drivers/misc/modem_if/modem_link_device_dpram.c index a650ed9..a87aff2 100644 --- a/drivers/misc/modem_if/modem_link_device_dpram.c +++ b/drivers/misc/modem_if/modem_link_device_dpram.c @@ -25,560 +25,385 @@ #include <linux/if_arp.h> #include <linux/platform_device.h> #include <linux/kallsyms.h> -#include <linux/platform_data/modem.h> +#include <linux/suspend.h> +#include <plat/gpio-cfg.h> +#include <mach/gpio.h> +#include "modem.h" #include "modem_prj.h" -#include "modem_link_device_dpram.h" #include "modem_utils.h" +#include "modem_link_device_dpram.h" -/* -** Function prototypes for basic DPRAM operations -*/ -static inline void clear_intr(struct dpram_link_device *dpld); -static inline u16 recv_intr(struct dpram_link_device *dpld); -static inline void send_intr(struct dpram_link_device *dpld, u16 mask); - -static inline u16 get_magic(struct dpram_link_device *dpld); -static inline void set_magic(struct dpram_link_device *dpld, u16 val); -static inline u16 get_access(struct dpram_link_device *dpld); -static inline void set_access(struct dpram_link_device *dpld, u16 val); - -static inline u32 get_tx_head(struct dpram_link_device *dpld, int id); -static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id); -static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in); -static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out); -static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id); -static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id); - -static inline u32 get_rx_head(struct dpram_link_device *dpld, int id); -static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id); -static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in); -static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out); -static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id); -static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id); - -static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id); -static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id); -static inline u16 get_mask_send(struct dpram_link_device *dpld, int id); - -static inline bool dpram_circ_valid(u32 size, u32 in, u32 out); - -static void handle_cp_crash(struct dpram_link_device *dpld); -static int trigger_force_cp_crash(struct dpram_link_device *dpld); -static void dpram_dump_memory(struct link_device *ld, char *buff); - -/* -** Functions for debugging -*/ -static inline void log_dpram_status(struct dpram_link_device *dpld) -{ - pr_info("mif: %s: {M:0x%X A:%d} {FMT TI:%u TO:%u RI:%u RO:%u} " - "{RAW TI:%u TO:%u RI:%u RO:%u} {INT:0x%X}\n", - dpld->ld.mc->name, - get_magic(dpld), get_access(dpld), - get_tx_head(dpld, IPC_FMT), get_tx_tail(dpld, IPC_FMT), - get_rx_head(dpld, IPC_FMT), get_rx_tail(dpld, IPC_FMT), - get_tx_head(dpld, IPC_RAW), get_tx_tail(dpld, IPC_RAW), - get_rx_head(dpld, IPC_RAW), get_rx_tail(dpld, IPC_RAW), - recv_intr(dpld)); -} - -static void set_dpram_map(struct dpram_link_device *dpld, - struct mif_irq_map *map) -{ - map->magic = get_magic(dpld); - map->access = get_access(dpld); - - map->fmt_tx_in = get_tx_head(dpld, IPC_FMT); - map->fmt_tx_out = get_tx_tail(dpld, IPC_FMT); - map->fmt_rx_in = get_rx_head(dpld, IPC_FMT); - map->fmt_rx_out = get_rx_tail(dpld, IPC_FMT); - map->raw_tx_in = get_tx_head(dpld, IPC_RAW); - map->raw_tx_out = get_tx_tail(dpld, IPC_RAW); - map->raw_rx_in = get_rx_head(dpld, IPC_RAW); - map->raw_rx_out = get_rx_tail(dpld, IPC_RAW); - - map->cp2ap = recv_intr(dpld); -} - -/* -** RXB (DPRAM RX buffer) functions -*/ -static struct dpram_rxb *rxbq_create_pool(unsigned size, int count) -{ - struct dpram_rxb *rxb; - u8 *buff; - int i; - - rxb = kzalloc(sizeof(struct dpram_rxb) * count, GFP_KERNEL); - if (!rxb) { - mif_info("ERR! kzalloc rxb fail\n"); - return NULL; - } - - buff = kzalloc((size * count), GFP_KERNEL|GFP_DMA); - if (!buff) { - mif_info("ERR! kzalloc buff fail\n"); - kfree(rxb); - return NULL; - } - - for (i = 0; i < count; i++) { - rxb[i].buff = buff; - rxb[i].size = size; - buff += size; - } - - return rxb; -} - -static inline unsigned rxbq_get_page_size(unsigned len) -{ - return ((len + PAGE_SIZE - 1) >> PAGE_SHIFT) << PAGE_SHIFT; -} - -static inline bool rxbq_empty(struct dpram_rxb_queue *rxbq) -{ - return (rxbq->in == rxbq->out) ? true : false; -} - -static inline int rxbq_free_size(struct dpram_rxb_queue *rxbq) -{ - int in = rxbq->in; - int out = rxbq->out; - int qsize = rxbq->size; - return (in < out) ? (out - in - 1) : (qsize + out - in - 1); -} +static void trigger_forced_cp_crash(struct dpram_link_device *dpld); -static inline struct dpram_rxb *rxbq_get_free_rxb(struct dpram_rxb_queue *rxbq) +/** + * set_circ_pointer + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @dir: direction of communication (TX or RX) + * @ptr: type of the queue pointer (HEAD or TAIL) + * @addr: address of the queue pointer + * @val: value to be written to the queue pointer + * + * Writes a value to a pointer in a circular queue with verification. + */ +static inline void set_circ_pointer(struct dpram_link_device *dpld, int id, + int dir, int ptr, void __iomem *addr, u16 val) { - struct dpram_rxb *rxb = NULL; + struct link_device *ld = &dpld->ld; + int cnt = 0; + u16 saved = 0; - if (likely(rxbq_free_size(rxbq) > 0)) { - rxb = &rxbq->rxb[rxbq->in]; - rxbq->in++; - if (rxbq->in >= rxbq->size) - rxbq->in -= rxbq->size; - rxb->data = rxb->buff; - } + iowrite16(val, addr); - return rxb; -} + while (1) { + /* Check the value written to the address */ + saved = ioread16(addr); + if (likely(saved == val)) + break; -static inline int rxbq_size(struct dpram_rxb_queue *rxbq) -{ - int in = rxbq->in; - int out = rxbq->out; - int qsize = rxbq->size; - return (in >= out) ? (in - out) : (qsize - out + in); -} + cnt++; + mif_err("%s: ERR! %s_%s.%s saved(%d) != val(%d), count %d\n", + ld->name, get_dev_name(id), circ_dir(dir), + circ_ptr(ptr), saved, val, cnt); + if (cnt >= MAX_RETRY_CNT) { + trigger_forced_cp_crash(dpld); + break; + } -static inline struct dpram_rxb *rxbq_get_data_rxb(struct dpram_rxb_queue *rxbq) -{ - struct dpram_rxb *rxb = NULL; + udelay(100); - if (likely(!rxbq_empty(rxbq))) { - rxb = &rxbq->rxb[rxbq->out]; - rxbq->out++; - if (rxbq->out >= rxbq->size) - rxbq->out -= rxbq->size; + /* Write the value again */ + iowrite16(val, addr); } - - return rxb; -} - -static inline u8 *rxb_put(struct dpram_rxb *rxb, unsigned len) -{ - rxb->len = len; - return rxb->data; } -static inline void rxb_clear(struct dpram_rxb *rxb) +/** + * recv_int2ap + * @dpld: pointer to an instance of dpram_link_device structure + * + * Returns the value of the CP-to-AP interrupt register in a DPRAM. + */ +static inline u16 recv_int2ap(struct dpram_link_device *dpld) { - rxb->data = NULL; - rxb->len = 0; + return ioread16(dpld->mbx2ap); } -/* -** DPRAM operations -*/ -static int dpram_register_isr(unsigned irq, irqreturn_t (*isr)(int, void*), - unsigned long flag, const char *name, - struct dpram_link_device *dpld) +/** + * send_int2cp + * @dpld: pointer to an instance of dpram_link_device structure + * @mask: value to be written to the AP-to-CP interrupt register in a DPRAM + */ +static inline void send_int2cp(struct dpram_link_device *dpld, u16 mask) { - int ret; + struct idpram_pm_op *pm_op = dpld->pm_op; - ret = request_irq(irq, isr, flag, name, dpld); - if (ret) { - mif_info("%s: ERR! request_irq fail (err %d)\n", name, ret); - return ret; + if (pm_op && pm_op->int2cp_possible) { + if (!pm_op->int2cp_possible(dpld)) + return; } - ret = enable_irq_wake(irq); - if (ret) - mif_info("%s: ERR! enable_irq_wake fail (err %d)\n", name, ret); - - mif_info("%s (#%d) handler registered\n", name, irq); - - return 0; -} - -static inline void clear_intr(struct dpram_link_device *dpld) -{ - if (likely(dpld->dpctl->clear_intr)) - dpld->dpctl->clear_intr(); + iowrite16(mask, dpld->mbx2cp); } -static inline u16 recv_intr(struct dpram_link_device *dpld) -{ - if (likely(dpld->dpctl->recv_intr)) - return dpld->dpctl->recv_intr(); - else - return ioread16(dpld->mbx2ap); -} - -static inline void send_intr(struct dpram_link_device *dpld, u16 mask) +/** + * read_int2cp + * @dpld: pointer to an instance of dpram_link_device structure + * + * Returns the value of the AP-to-CP interrupt register in a DPRAM. + */ +static inline u16 read_int2cp(struct dpram_link_device *dpld) { - if (likely(dpld->dpctl->send_intr)) - dpld->dpctl->send_intr(mask); - else - iowrite16(mask, dpld->mbx2cp); + return ioread16(dpld->mbx2cp); } +/** + * get_magic + * @dpld: pointer to an instance of dpram_link_device structure + * + * Returns the value of the "magic code" field in a DPRAM. + */ static inline u16 get_magic(struct dpram_link_device *dpld) { return ioread16(dpld->magic); } +/** + * set_magic + * @dpld: pointer to an instance of dpram_link_device structure + * @val: value to be written to the "magic code" field in a DPRAM + */ static inline void set_magic(struct dpram_link_device *dpld, u16 val) { iowrite16(val, dpld->magic); } +/** + * get_access + * @dpld: pointer to an instance of dpram_link_device structure + * + * Returns the value of the "access enable" field in a DPRAM. + */ static inline u16 get_access(struct dpram_link_device *dpld) { return ioread16(dpld->access); } +/** + * set_access + * @dpld: pointer to an instance of dpram_link_device structure + * @val: value to be written to the "access enable" field in a DPRAM + */ static inline void set_access(struct dpram_link_device *dpld, u16 val) { iowrite16(val, dpld->access); } -static inline u32 get_tx_head(struct dpram_link_device *dpld, int id) +/** + * get_txq_head + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a head (in) pointer in a TX queue. + */ +static inline u32 get_txq_head(struct dpram_link_device *dpld, int id) { return ioread16(dpld->dev[id]->txq.head); } -static inline u32 get_tx_tail(struct dpram_link_device *dpld, int id) +/** + * get_txq_tail + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a tail (out) pointer in a TX queue. + * + * It is useless for an AP to read a tail pointer in a TX queue twice to verify + * whether or not the value in the pointer is valid, because it can already have + * been updated by a CP after the first access from the AP. + */ +static inline u32 get_txq_tail(struct dpram_link_device *dpld, int id) { return ioread16(dpld->dev[id]->txq.tail); } -static inline void set_tx_head(struct dpram_link_device *dpld, int id, u32 in) -{ - int cnt = 3; - u32 val = 0; - - iowrite16((u16)in, dpld->dev[id]->txq.head); - - do { - /* Check head value written */ - val = ioread16(dpld->dev[id]->txq.head); - if (likely(val == in)) - return; - - mif_err("ERR: %s txq.head(%d) != in(%d)\n", - get_dev_name(id), val, in); - udelay(100); - - /* Write head value again */ - iowrite16((u16)in, dpld->dev[id]->txq.head); - } while (cnt--); - - trigger_force_cp_crash(dpld); -} - -static inline void set_tx_tail(struct dpram_link_device *dpld, int id, u32 out) -{ - int cnt = 3; - u32 val = 0; - - iowrite16((u16)out, dpld->dev[id]->txq.tail); - - do { - /* Check tail value written */ - val = ioread16(dpld->dev[id]->txq.tail); - if (likely(val == out)) - return; - - mif_err("ERR: %s txq.tail(%d) != out(%d)\n", - get_dev_name(id), val, out); - udelay(100); - - /* Write tail value again */ - iowrite16((u16)out, dpld->dev[id]->txq.tail); - } while (cnt--); - - trigger_force_cp_crash(dpld); -} - -static inline u8 *get_tx_buff(struct dpram_link_device *dpld, int id) +/** + * get_txq_buff + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the start address of the buffer in a TXQ. + */ +static inline u8 *get_txq_buff(struct dpram_link_device *dpld, int id) { return dpld->dev[id]->txq.buff; } -static inline u32 get_tx_buff_size(struct dpram_link_device *dpld, int id) +/** + * get_txq_buff_size + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the size of the buffer in a TXQ. + */ +static inline u32 get_txq_buff_size(struct dpram_link_device *dpld, int id) { return dpld->dev[id]->txq.size; } -static inline u32 get_rx_head(struct dpram_link_device *dpld, int id) +/** + * get_rxq_head + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a head (in) pointer in an RX queue. + * + * It is useless for an AP to read a head pointer in an RX queue twice to verify + * whether or not the value in the pointer is valid, because it can already have + * been updated by a CP after the first access from the AP. + */ +static inline u32 get_rxq_head(struct dpram_link_device *dpld, int id) { return ioread16(dpld->dev[id]->rxq.head); } -static inline u32 get_rx_tail(struct dpram_link_device *dpld, int id) +/** + * get_rxq_tail + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a tail (in) pointer in an RX queue. + */ +static inline u32 get_rxq_tail(struct dpram_link_device *dpld, int id) { return ioread16(dpld->dev[id]->rxq.tail); } -static inline void set_rx_head(struct dpram_link_device *dpld, int id, u32 in) -{ - int cnt = 3; - u32 val = 0; - - iowrite16((u16)in, dpld->dev[id]->rxq.head); - - do { - /* Check head value written */ - val = ioread16(dpld->dev[id]->rxq.head); - if (val == in) - return; - - mif_err("ERR: %s rxq.head(%d) != in(%d)\n", - get_dev_name(id), val, in); - udelay(100); - - /* Write head value again */ - iowrite16((u16)in, dpld->dev[id]->rxq.head); - } while (cnt--); - - trigger_force_cp_crash(dpld); -} - -static inline void set_rx_tail(struct dpram_link_device *dpld, int id, u32 out) -{ - int cnt = 3; - u32 val = 0; - - iowrite16((u16)out, dpld->dev[id]->rxq.tail); - - do { - /* Check tail value written */ - val = ioread16(dpld->dev[id]->rxq.tail); - if (val == out) - return; - - mif_err("ERR: %s rxq.tail(%d) != out(%d)\n", - get_dev_name(id), val, out); - udelay(100); - - /* Write tail value again */ - iowrite16((u16)out, dpld->dev[id]->rxq.tail); - } while (cnt--); - - trigger_force_cp_crash(dpld); -} - -static inline u8 *get_rx_buff(struct dpram_link_device *dpld, int id) +/** + * get_rxq_buff + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the start address of the buffer in an RXQ. + */ +static inline u8 *get_rxq_buff(struct dpram_link_device *dpld, int id) { return dpld->dev[id]->rxq.buff; } -static inline u32 get_rx_buff_size(struct dpram_link_device *dpld, int id) +/** + * get_rxq_buff_size + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the size of the buffer in an RXQ. + */ +static inline u32 get_rxq_buff_size(struct dpram_link_device *dpld, int id) { return dpld->dev[id]->rxq.size; } -static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id) +/** + * set_txq_head + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @in: value to be written to the head pointer in a TXQ + */ +static inline void set_txq_head(struct dpram_link_device *dpld, int id, u32 in) { - return dpld->dev[id]->mask_req_ack; + set_circ_pointer(dpld, id, TX, HEAD, dpld->dev[id]->txq.head, in); } -static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id) +/** + * set_txq_tail + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @out: value to be written to the tail pointer in a TXQ + */ +static inline void set_txq_tail(struct dpram_link_device *dpld, int id, u32 out) { - return dpld->dev[id]->mask_res_ack; + set_circ_pointer(dpld, id, TX, TAIL, dpld->dev[id]->txq.tail, out); } -static inline u16 get_mask_send(struct dpram_link_device *dpld, int id) +/** + * set_rxq_head + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @in: value to be written to the head pointer in an RXQ + */ +static inline void set_rxq_head(struct dpram_link_device *dpld, int id, u32 in) { - return dpld->dev[id]->mask_send; + set_circ_pointer(dpld, id, RX, HEAD, dpld->dev[id]->rxq.head, in); } -static inline bool dpram_circ_valid(u32 size, u32 in, u32 out) +/** + * set_rxq_tail + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @out: value to be written to the tail pointer in an RXQ + */ +static inline void set_rxq_tail(struct dpram_link_device *dpld, int id, u32 out) { - if (in >= size) - return false; - - if (out >= size) - return false; - - return true; + set_circ_pointer(dpld, id, RX, TAIL, dpld->dev[id]->rxq.tail, out); } -/* Get free space in the TXQ as well as in & out pointers */ -static inline int dpram_get_txq_space(struct dpram_link_device *dpld, int dev, - u32 qsize, u32 *in, u32 *out) -{ - struct link_device *ld = &dpld->ld; - int cnt = 3; - u32 head; - u32 tail; - int space; - - do { - head = get_tx_head(dpld, dev); - tail = get_tx_tail(dpld, dev); - - space = (head < tail) ? (tail - head - 1) : - (qsize + tail - head - 1); - mif_debug("%s: %s_TXQ qsize[%u] in[%u] out[%u] space[%u]\n", - ld->name, get_dev_name(dev), qsize, head, tail, space); - - if (dpram_circ_valid(qsize, head, tail)) { - *in = head; - *out = tail; - return space; - } - - mif_info("%s: CAUTION! <%pf> " - "%s_TXQ invalid (size:%d in:%d out:%d)\n", - ld->name, __builtin_return_address(0), - get_dev_name(dev), qsize, head, tail); - - udelay(100); - } while (cnt--); - - *in = 0; - *out = 0; - return -EINVAL; -} - -static void dpram_reset_tx_circ(struct dpram_link_device *dpld, int dev) +/** + * get_mask_req_ack + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the REQ_ACK mask value for the IPC device. + */ +static inline u16 get_mask_req_ack(struct dpram_link_device *dpld, int id) { - set_tx_head(dpld, dev, 0); - set_tx_tail(dpld, dev, 0); - if (dev == IPC_FMT) - trigger_force_cp_crash(dpld); + return dpld->dev[id]->mask_req_ack; } -/* Get data size in the RXQ as well as in & out pointers */ -static inline int dpram_get_rxq_rcvd(struct dpram_link_device *dpld, int dev, - u32 qsize, u32 *in, u32 *out) +/** + * get_mask_res_ack + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the RES_ACK mask value for the IPC device. + */ +static inline u16 get_mask_res_ack(struct dpram_link_device *dpld, int id) { - struct link_device *ld = &dpld->ld; - int cnt = 3; - u32 head; - u32 tail; - u32 rcvd; - - do { - head = get_rx_head(dpld, dev); - tail = get_rx_tail(dpld, dev); - if (head == tail) { - *in = head; - *out = tail; - return 0; - } - - rcvd = (head > tail) ? (head - tail) : (qsize - tail + head); - mif_debug("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n", - ld->name, get_dev_name(dev), qsize, head, tail, rcvd); - - if (dpram_circ_valid(qsize, head, tail)) { - *in = head; - *out = tail; - return rcvd; - } - - mif_info("%s: CAUTION! <%pf> " - "%s_RXQ invalid (size:%d in:%d out:%d)\n", - ld->name, __builtin_return_address(0), - get_dev_name(dev), qsize, head, tail); - - udelay(100); - } while (cnt--); - - *in = 0; - *out = 0; - return -EINVAL; + return dpld->dev[id]->mask_res_ack; } -static void dpram_reset_rx_circ(struct dpram_link_device *dpld, int dev) +/** + * get_mask_send + * @dpld: pointer to an instance of dpram_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the SEND mask value for the IPC device. + */ +static inline u16 get_mask_send(struct dpram_link_device *dpld, int id) { - set_rx_head(dpld, dev, 0); - set_rx_tail(dpld, dev, 0); - if (dev == IPC_FMT) - trigger_force_cp_crash(dpld); + return dpld->dev[id]->mask_send; } -/* -** CAUTION : dpram_allow_sleep() MUST be invoked after dpram_wake_up() success -*/ -static int dpram_wake_up(struct dpram_link_device *dpld) +/** + * reset_txq_circ + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Empties a TXQ by resetting the head (in) pointer with the value in the tail + * (out) pointer. + */ +static inline void reset_txq_circ(struct dpram_link_device *dpld, int dev) { struct link_device *ld = &dpld->ld; + u32 head = get_txq_head(dpld, dev); + u32 tail = get_txq_tail(dpld, dev); - if (!dpld->dpctl->wakeup) - return 0; - - if (dpld->dpctl->wakeup() < 0) { - mif_err("%s: ERR! <%pf> DPRAM wakeup fail once\n", - ld->name, __builtin_return_address(0)); - - if (dpld->dpctl->sleep) - dpld->dpctl->sleep(); + mif_info("%s: %s_TXQ: HEAD[%u] <== TAIL[%u]\n", + ld->name, get_dev_name(dev), head, tail); - udelay(10); - - if (dpld->dpctl->wakeup() < 0) { - mif_err("%s: ERR! <%pf> DPRAM wakeup fail twice\n", - ld->name, __builtin_return_address(0)); - return -EACCES; - } - } - - atomic_inc(&dpld->accessing); - return 0; + set_txq_head(dpld, dev, tail); } -static void dpram_allow_sleep(struct dpram_link_device *dpld) +/** + * reset_rxq_circ + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Empties an RXQ by resetting the tail (out) pointer with the value in the head + * (in) pointer. + */ +static inline void reset_rxq_circ(struct dpram_link_device *dpld, int dev) { struct link_device *ld = &dpld->ld; + u32 head = get_rxq_head(dpld, dev); + u32 tail = get_rxq_tail(dpld, dev); - if (!dpld->dpctl->sleep) - return; + mif_info("%s: %s_RXQ: TAIL[%u] <== HEAD[%u]\n", + ld->name, get_dev_name(dev), tail, head); - if (atomic_dec_return(&dpld->accessing) <= 0) { - dpld->dpctl->sleep(); - atomic_set(&dpld->accessing, 0); - mif_debug("%s: DPRAM sleep possible\n", ld->name); - } + set_rxq_tail(dpld, dev, head); } -static int dpram_check_access(struct dpram_link_device *dpld) +/** + * check_magic_access + * @dpld: pointer to an instance of dpram_link_device structure + * + * Returns 0 if the "magic code" and "access enable" values are valid, otherwise + * returns -EACCES. + */ +static int check_magic_access(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; int i; u16 magic = get_magic(dpld); u16 access = get_access(dpld); + /* Returns 0 if the "magic code" and "access enable" are valid */ if (likely(magic == DPRAM_MAGIC_CODE && access == 1)) return 0; + /* Retry up to 100 times with 100 us delay per each retry */ for (i = 1; i <= 100; i++) { - mif_info("%s: ERR! magic:%X access:%X -> retry:%d\n", + mif_info("%s: magic:%X access:%X -> retry:%d\n", ld->name, magic, access, i); udelay(100); @@ -592,357 +417,313 @@ static int dpram_check_access(struct dpram_link_device *dpld) return -EACCES; } -static bool dpram_ipc_active(struct dpram_link_device *dpld) +/** + * ipc_active + * @dpld: pointer to an instance of dpram_link_device structure + * + * Returns whether or not IPC via the dpram_link_device instance is possible. + */ +static bool ipc_active(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; /* Check DPRAM mode */ if (ld->mode != LINK_MODE_IPC) { - mif_info("%s: <%pf> ld->mode != LINK_MODE_IPC\n", - ld->name, __builtin_return_address(0)); + mif_err("%s: <called by %pf> ERR! ld->mode != LINK_MODE_IPC\n", + ld->name, CALLER); return false; } - if (dpram_check_access(dpld) < 0) { - mif_info("%s: ERR! <%pf> dpram_check_access fail\n", - ld->name, __builtin_return_address(0)); + /* Check "magic code" and "access enable" values */ + if (check_magic_access(dpld) < 0) { + mif_err("%s: <called by %pf> ERR! check_magic_access fail\n", + ld->name, CALLER); return false; } return true; } -static void dpram_ipc_write(struct dpram_link_device *dpld, int dev, - u32 qsize, u32 in, u32 out, struct sk_buff *skb) -{ - struct link_device *ld = &dpld->ld; - u8 __iomem *buff = get_tx_buff(dpld, dev); - u8 *src = skb->data; - u32 len = skb->len; - u32 inp; - struct mif_irq_map map; - - if (in < out) { - /* +++++++++ in ---------- out ++++++++++ */ - memcpy((buff + in), src, len); - } else { - /* ------ out +++++++++++ in ------------ */ - u32 space = qsize - in; - - /* 1) in -> buffer end */ - memcpy((buff + in), src, ((len > space) ? space : len)); - - /* 2) buffer start -> out */ - if (len > space) - memcpy(buff, (src + space), (len - space)); - } - - /* update new in pointer */ - inp = in + len; - if (inp >= qsize) - inp -= qsize; - set_tx_head(dpld, dev, inp); +/** + * get_dpram_status + * @dpld: pointer to an instance of dpram_link_device structure + * @dir: direction of communication (TX or RX) + * @stat: pointer to an instance of mem_status structure + * + * Takes a snapshot of the current status of a DPRAM. + */ +static void get_dpram_status(struct dpram_link_device *dpld, + enum circ_dir_type dir, struct mem_status *stat) +{ +#ifdef DEBUG_MODEM_IF + getnstimeofday(&stat->ts); +#endif + + stat->dir = dir; + stat->magic = get_magic(dpld); + stat->access = get_access(dpld); + stat->head[IPC_FMT][TX] = get_txq_head(dpld, IPC_FMT); + stat->tail[IPC_FMT][TX] = get_txq_tail(dpld, IPC_FMT); + stat->head[IPC_FMT][RX] = get_rxq_head(dpld, IPC_FMT); + stat->tail[IPC_FMT][RX] = get_rxq_tail(dpld, IPC_FMT); + stat->head[IPC_RAW][TX] = get_txq_head(dpld, IPC_RAW); + stat->tail[IPC_RAW][TX] = get_txq_tail(dpld, IPC_RAW); + stat->head[IPC_RAW][RX] = get_rxq_head(dpld, IPC_RAW); + stat->tail[IPC_RAW][RX] = get_rxq_tail(dpld, IPC_RAW); + stat->int2ap = recv_int2ap(dpld); + stat->int2cp = read_int2cp(dpld); +} + +#if 0 +/** + * save_ipc_trace_work + * @work: pointer to an instance of work_struct structure + * + * Performs actual file operation for saving RX IPC trace. + */ +static void save_ipc_trace_work(struct work_struct *work) +{ + struct dpram_link_device *dpld; + struct link_device *ld; + struct trace_data_queue *trq; + struct trace_data *trd; + struct circ_status *stat; + struct file *fp; + struct timespec *ts; + int dev; + u8 *dump; + int rcvd; + u8 *buff; + char *path; + struct utc_time utc; - if (dev == IPC_FMT) { - set_dpram_map(dpld, &map); - mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write")); - mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len); - } + dpld = container_of(work, struct dpram_link_device, trace_dwork.work); + ld = &dpld->ld; + trq = &dpld->trace_list; + path = dpld->trace_path; - if (ld->aligned && memcmp16_to_io((buff + in), src, 4)) { - mif_err("%s: memcmp16_to_io fail\n", ld->name); - trigger_force_cp_crash(dpld); + buff = kzalloc(dpld->size << 3, GFP_KERNEL); + if (!buff) { + while (1) { + trd = trq_get_data_slot(trq); + if (!trd) + break; + + ts = &trd->ts; + dev = trd->dev; + stat = &trd->circ_stat; + dump = trd->data; + rcvd = trd->size; + print_ipc_trace(ld, dev, stat, ts, dump, rcvd); + + kfree(dump); + } + return; } -} - -static int dpram_try_ipc_tx(struct dpram_link_device *dpld, int dev) -{ - struct link_device *ld = &dpld->ld; - struct sk_buff_head *txq = ld->skb_txq[dev]; - struct sk_buff *skb; - unsigned long int flags; - u32 qsize = get_tx_buff_size(dpld, dev); - u32 in; - u32 out; - int space; - int copied = 0; - - spin_lock_irqsave(&dpld->tx_lock[dev], flags); while (1) { - space = dpram_get_txq_space(dpld, dev, qsize, &in, &out); - if (unlikely(space < 0)) { - spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); - dpram_reset_tx_circ(dpld, dev); - return space; - } - - skb = skb_dequeue(txq); - if (unlikely(!skb)) + trd = trq_get_data_slot(trq); + if (!trd) break; - if (unlikely(space < skb->len)) { - atomic_set(&dpld->res_required[dev], 1); - skb_queue_head(txq, skb); - spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); - mif_info("%s: %s " - "qsize[%u] in[%u] out[%u] free[%u] < len[%u]\n", - ld->name, get_dev_name(dev), - qsize, in, out, space, skb->len); - return -ENOSPC; + ts = &trd->ts; + dev = trd->dev; + stat = &trd->circ_stat; + dump = trd->data; + rcvd = trd->size; + + ts2utc(ts, &utc); + snprintf(path, MIF_MAX_PATH_LEN, + "%s/%s_%s_%d%02d%02d-%02d%02d%02d.lst", + MIF_LOG_DIR, ld->name, get_dev_name(dev), + utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec); + + fp = mif_open_file(path); + if (fp) { + int len; + + snprintf(buff, MIF_MAX_PATH_LEN, + "[%d-%02d-%02d %02d:%02d:%02d.%03d] " + "%s %s_RXQ {IN:%u OUT:%u LEN:%d}\n", + utc.year, utc.mon, utc.day, utc.hour, utc.min, + utc.sec, utc.msec, ld->name, get_dev_name(dev), + stat->in, stat->out, stat->size); + len = strlen(buff); + mif_dump2format4(dump, rcvd, (buff + len), NULL); + strcat(buff, "\n"); + len = strlen(buff); + + mif_save_file(fp, buff, len); + + memset(buff, 0, len); + mif_close_file(fp); + } else { + mif_err("%s: %s open fail\n", ld->name, path); + print_ipc_trace(ld, dev, stat, ts, dump, rcvd); } - /* TX if there is enough room in the queue */ - dpram_ipc_write(dpld, dev, qsize, in, out, skb); - copied += skb->len; - dev_kfree_skb_any(skb); + kfree(dump); } - spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); - - return copied; -} - -static void dpram_ipc_rx_task(unsigned long data) -{ - struct dpram_link_device *dpld = (struct dpram_link_device *)data; - struct link_device *ld = &dpld->ld; - struct io_device *iod; - struct dpram_rxb *rxb; - unsigned qlen; - int i; - - for (i = 0; i < dpld->max_ipc_dev; i++) { - iod = dpld->iod[i]; - qlen = rxbq_size(&dpld->rxbq[i]); - while (qlen > 0) { - rxb = rxbq_get_data_rxb(&dpld->rxbq[i]); - iod->recv(iod, ld, rxb->data, rxb->len); - rxb_clear(rxb); - qlen--; - } - } + kfree(buff); } +#endif -static void dpram_ipc_read(struct dpram_link_device *dpld, int dev, u8 *dst, - u8 __iomem *src, u32 out, u32 len, u32 qsize) +/** + * set_dpram_map + * @dpld: pointer to an instance of dpram_link_device structure + * @map: pointer to an instance of mif_irq_map structure + * + * Sets variables in an mif_irq_map instance as current DPRAM status for IPC + * logging. + */ +static void set_dpram_map(struct dpram_link_device *dpld, + struct mif_irq_map *map) { - if ((out + len) <= qsize) { - /* ----- (out) (in) ----- */ - /* ----- 7f 00 00 7e ----- */ - memcpy(dst, (src + out), len); - } else { - /* (in) ----------- (out) */ - /* 00 7e ----------- 7f 00 */ - unsigned len1 = qsize - out; + map->magic = get_magic(dpld); + map->access = get_access(dpld); - /* 1) out -> buffer end */ - memcpy(dst, (src + out), len1); + map->fmt_tx_in = get_txq_head(dpld, IPC_FMT); + map->fmt_tx_out = get_txq_tail(dpld, IPC_FMT); + map->fmt_rx_in = get_rxq_head(dpld, IPC_FMT); + map->fmt_rx_out = get_rxq_tail(dpld, IPC_FMT); + map->raw_tx_in = get_txq_head(dpld, IPC_RAW); + map->raw_tx_out = get_txq_tail(dpld, IPC_RAW); + map->raw_rx_in = get_rxq_head(dpld, IPC_RAW); + map->raw_rx_out = get_rxq_tail(dpld, IPC_RAW); - /* 2) buffer start -> in */ - dst += len1; - memcpy(dst, src, (len - len1)); - } + map->cp2ap = recv_int2ap(dpld); } -/* - ret < 0 : error - ret == 0 : no data - ret > 0 : valid data -*/ -static int dpram_ipc_recv_data_with_rxb(struct dpram_link_device *dpld, int dev) +/** + * dpram_wake_up + * @dpld: pointer to an instance of dpram_link_device structure + * + * Wakes up a DPRAM if it can sleep and increases the "accessing" counter in the + * dpram_link_device instance. + * + * CAUTION!!! dpram_allow_sleep() MUST be invoked after dpram_wake_up() success + * to decrease the "accessing" counter. + */ +static int dpram_wake_up(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; - struct dpram_rxb *rxb; - u8 __iomem *src = get_rx_buff(dpld, dev); - u32 qsize = get_rx_buff_size(dpld, dev); - u32 in; - u32 out; - u32 rcvd; - struct mif_irq_map map; - - rcvd = dpram_get_rxq_rcvd(dpld, dev, qsize, &in, &out); - if (rcvd <= 0) - return rcvd; - if (dev == IPC_FMT) { - set_dpram_map(dpld, &map); - mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); - } + if (unlikely(!dpld->need_wake_up)) + return 0; - /* Allocate an rxb */ - rxb = rxbq_get_free_rxb(&dpld->rxbq[dev]); - if (!rxb) { - mif_info("%s: ERR! %s rxbq_get_free_rxb fail\n", - ld->name, get_dev_name(dev)); - return -ENOMEM; + if (dpld->ext_op->wakeup(dpld) < 0) { + mif_err("%s: <called by %pf> ERR! wakeup fail\n", + ld->name, CALLER); + return -EACCES; } - /* Read data from each DPRAM buffer */ - dpram_ipc_read(dpld, dev, rxb_put(rxb, rcvd), src, out, rcvd, qsize); - - /* Calculate and set new out */ - out += rcvd; - if (out >= qsize) - out -= qsize; - set_rx_tail(dpld, dev, out); + atomic_inc(&dpld->accessing); - return rcvd; + return 0; } -/* - ret < 0 : error - ret == 0 : no data - ret > 0 : valid data -*/ -static int dpram_ipc_recv_data_with_skb(struct dpram_link_device *dpld, int dev) +/** + * dpram_allow_sleep + * @dpld: pointer to an instance of dpram_link_device structure + * + * Decreases the "accessing" counter in the dpram_link_device instance if it can + * sleep and allows the DPRAM to sleep only if the value of "accessing" counter + * is less than or equal to 0. + * + * MUST be invoked after dpram_wake_up() success to decrease the "accessing" + * counter. + */ +static void dpram_allow_sleep(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; - struct io_device *iod = dpld->iod[dev]; - struct sk_buff *skb; - u8 __iomem *src = get_rx_buff(dpld, dev); - u32 qsize = get_rx_buff_size(dpld, dev); - u32 in; - u32 out; - u32 rcvd; - int rest; - u8 *frm; - u8 *dst; - unsigned int len; - unsigned int pad; - unsigned int tot; - struct mif_irq_map map; - - rcvd = dpram_get_rxq_rcvd(dpld, dev, qsize, &in, &out); - if (rcvd <= 0) - return rcvd; - - if (dev == IPC_FMT) { - set_dpram_map(dpld, &map); - mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); - } - - rest = rcvd; - while (rest > 0) { - frm = src + out; - if (unlikely(!sipc5_start_valid(frm[0]))) { - mif_err("%s: ERR! %s invalid start 0x%02X\n", - ld->name, get_dev_name(dev), frm[0]); - skb_queue_purge(&dpld->skb_rxq[dev]); - return -EBADMSG; - } - - len = sipc5_get_frame_sz16(frm); - if (unlikely(len > rest)) { - mif_err("%s: ERR! %s len %d > rest %d\n", - ld->name, get_dev_name(dev), len, rest); - skb_queue_purge(&dpld->skb_rxq[dev]); - return -EBADMSG; - } - - pad = sipc5_calc_padding_size(len); - tot = len + pad; - - /* Allocate an skb */ - skb = dev_alloc_skb(tot); - if (!skb) { - mif_err("%s: ERR! %s dev_alloc_skb fail\n", - ld->name, get_dev_name(dev)); - return -ENOMEM; - } - /* Read data from each DPRAM buffer */ - dst = skb_put(skb, tot); - dpram_ipc_read(dpld, dev, dst, src, out, tot, qsize); - skb_trim(skb, len); - iod->recv_skb(iod, ld, skb); + if (unlikely(!dpld->need_wake_up)) + return; - /* Calculate and set new out */ - rest -= tot; - out += tot; - if (out >= qsize) - out -= qsize; + if (atomic_dec_return(&dpld->accessing) <= 0) { + dpld->ext_op->sleep(dpld); + atomic_set(&dpld->accessing, 0); + mif_debug("%s: DPRAM sleep possible\n", ld->name); } - - set_rx_tail(dpld, dev, out); - - return rcvd; } -static void non_command_handler(struct dpram_link_device *dpld, u16 intr) +static int capture_dpram_snapshot(struct link_device *ld, struct io_device *iod) { - struct link_device *ld = &dpld->ld; - int i = 0; - int ret = 0; - u16 tx_mask = 0; + struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct sk_buff *skb; + u32 size = dpld->size; + u32 copied = 0; + u8 *dump; - if (!dpram_ipc_active(dpld)) - return; + dpram_wake_up(dpld); + dump = capture_mem_dump(ld, dpld->base, dpld->size); + dpram_allow_sleep(dpld); - /* Read data from DPRAM */ - for (i = 0; i < dpld->max_ipc_dev; i++) { - if (dpld->use_skb) - ret = dpram_ipc_recv_data_with_skb(dpld, i); - else - ret = dpram_ipc_recv_data_with_rxb(dpld, i); - if (ret < 0) - dpram_reset_rx_circ(dpld, i); + if (!dump) + return -ENOMEM; - /* Check and process REQ_ACK (at this time, in == out) */ - if (intr & get_mask_req_ack(dpld, i)) { - mif_debug("%s: send %s_RES_ACK\n", - ld->name, get_dev_name(i)); - tx_mask |= get_mask_res_ack(dpld, i); + while (copied < size) { + skb = alloc_skb(MAX_DUMP_SKB_SIZE, GFP_ATOMIC); + if (!skb) { + mif_err("ERR! alloc_skb fail\n"); + kfree(dump); + return -ENOMEM; } - } - if (!dpld->use_skb) { - /* Schedule soft IRQ for RX */ - tasklet_hi_schedule(&dpld->rx_tsk); - } + skb_put(skb, MAX_DUMP_SKB_SIZE); + memcpy(skb->data, (dump + copied), MAX_DUMP_SKB_SIZE); + copied += MAX_DUMP_SKB_SIZE; - /* Try TX via DPRAM */ - for (i = 0; i < dpld->max_ipc_dev; i++) { - if (atomic_read(&dpld->res_required[i]) > 0) { - ret = dpram_try_ipc_tx(dpld, i); - if (ret > 0) { - atomic_set(&dpld->res_required[i], 0); - tx_mask |= get_mask_send(dpld, i); - } else if (ret == -ENOSPC) { - tx_mask |= get_mask_req_ack(dpld, i); - } - } + skb_queue_tail(&iod->sk_rx_q, skb); + wake_up(&iod->wq); } - if (tx_mask) { - send_intr(dpld, INT_NON_CMD(tx_mask)); - mif_debug("%s: send intr 0x%04X\n", ld->name, tx_mask); - } + kfree(dump); + return 0; } +/** + * handle_cp_crash + * @dpld: pointer to an instance of dpram_link_device structure + * + * Actual handler for the CRASH_EXIT command from a CP. + */ static void handle_cp_crash(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; struct io_device *iod; int i; - for (i = 0; i < dpld->max_ipc_dev; i++) { - mif_info("%s: purging %s_skb_txq\b", ld->name, get_dev_name(i)); + if (dpld->forced_cp_crash) + dpld->forced_cp_crash = false; + + /* Stop network interfaces */ + mif_netif_stop(ld); + + /* Purge the skb_txq in every IPC device (IPC_FMT, IPC_RAW, etc.) */ + for (i = 0; i < ld->max_ipc_dev; i++) skb_queue_purge(ld->skb_txq[i]); - } + /* Change the modem state to STATE_CRASH_EXIT for the FMT IO device */ iod = link_get_iod_with_format(ld, IPC_FMT); - iod->modem_state_changed(iod, STATE_CRASH_EXIT); + if (iod) + iod->modem_state_changed(iod, STATE_CRASH_EXIT); + /* Change the modem state to STATE_CRASH_EXIT for the BOOT IO device */ iod = link_get_iod_with_format(ld, IPC_BOOT); - iod->modem_state_changed(iod, STATE_CRASH_EXIT); - - iod = link_get_iod_with_channel(ld, PS_DATA_CH_0); if (iod) - iodevs_for_each(iod->msd, iodev_netif_stop, 0); + iod->modem_state_changed(iod, STATE_CRASH_EXIT); } -static void handle_no_crash_ack(unsigned long arg) +/** + * handle_no_cp_crash_ack + * @arg: pointer to an instance of dpram_link_device structure + * + * Invokes handle_cp_crash() to enter the CRASH_EXIT state if there was no + * CRASH_ACK from a CP in FORCE_CRASH_ACK_TIMEOUT. + */ +static void handle_no_cp_crash_ack(unsigned long arg) { struct dpram_link_device *dpld = (struct dpram_link_device *)arg; struct link_device *ld = &dpld->ld; @@ -955,165 +736,341 @@ static void handle_no_crash_ack(unsigned long arg) handle_cp_crash(dpld); } -static int trigger_force_cp_crash(struct dpram_link_device *dpld) +/** + * trigger_forced_cp_crash + * @dpld: pointer to an instance of dpram_link_device structure + * + * Triggers an enforced CP crash. + */ +static void trigger_forced_cp_crash(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; +#ifdef DEBUG_MODEM_IF + struct trace_data *trd; + u8 *dump; + struct timespec ts; + getnstimeofday(&ts); +#endif if (ld->mode == LINK_MODE_ULOAD) { - mif_err("%s: CP crash is already in progress\n", ld->mc->name); - return 0; + mif_err("%s: <called by %pf> ALREADY in progress\n", + ld->name, CALLER); + return; } ld->mode = LINK_MODE_ULOAD; - mif_err("%s: called by %pf\n", ld->name, __builtin_return_address(0)); + dpld->forced_cp_crash = true; + + disable_irq_nosync(dpld->irq); + + dpram_wake_up(dpld); + +#ifdef DEBUG_MODEM_IF + dump = capture_mem_dump(ld, dpld->base, dpld->size); + if (dump) { + trd = trq_get_free_slot(&dpld->trace_list); + memcpy(&trd->ts, &ts, sizeof(struct timespec)); + trd->dev = IPC_DEBUG; + trd->data = dump; + trd->size = dpld->size; + } +#endif - if (dpld->dp_type == CP_IDPRAM) - dpram_wake_up(dpld); + enable_irq(dpld->irq); - send_intr(dpld, INT_CMD(INT_CMD_CRASH_EXIT)); + mif_err("%s: <called by %pf>\n", ld->name, CALLER); + /* Send CRASH_EXIT command to a CP */ + send_int2cp(dpld, INT_CMD(INT_CMD_CRASH_EXIT)); + get_dpram_status(dpld, TX, msq_get_free_slot(&dpld->stat_list)); + + /* If there is no CRASH_ACK from a CP in FORCE_CRASH_ACK_TIMEOUT, + handle_no_cp_crash_ack() will be executed. */ mif_add_timer(&dpld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT, - handle_no_crash_ack, (unsigned long)dpld); + handle_no_cp_crash_ack, (unsigned long)dpld); - return 0; + return; } -static int dpram_init_ipc(struct dpram_link_device *dpld) +/** + * ext_command_handler + * @dpld: pointer to an instance of dpram_link_device structure + * @cmd: extended DPRAM command from a CP + * + * Processes an extended command from a CP. + */ +static void ext_command_handler(struct dpram_link_device *dpld, u16 cmd) { struct link_device *ld = &dpld->ld; - int i; - - if (ld->mode == LINK_MODE_IPC && - get_magic(dpld) == DPRAM_MAGIC_CODE && - get_access(dpld) == 1) - mif_info("%s: IPC already initialized\n", ld->name); + u16 resp; - /* Clear pointers in every circular queue */ - for (i = 0; i < dpld->max_ipc_dev; i++) { - set_tx_head(dpld, i, 0); - set_tx_tail(dpld, i, 0); - set_rx_head(dpld, i, 0); - set_rx_tail(dpld, i, 0); - } + switch (EXT_CMD_MASK(cmd)) { + case EXT_CMD_SET_SPEED_LOW: + if (dpld->dpram->setup_speed) { + dpld->dpram->setup_speed(DPRAM_SPEED_LOW); + resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_LOW); + send_int2cp(dpld, resp); + } + break; - /* Initialize variables for efficient TX/RX processing */ - for (i = 0; i < dpld->max_ipc_dev; i++) - dpld->iod[i] = link_get_iod_with_format(ld, i); - dpld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW); + case EXT_CMD_SET_SPEED_MID: + if (dpld->dpram->setup_speed) { + dpld->dpram->setup_speed(DPRAM_SPEED_MID); + resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_MID); + send_int2cp(dpld, resp); + } + break; - if (dpld->iod[IPC_RAW]->recv_skb) - dpld->use_skb = true; + case EXT_CMD_SET_SPEED_HIGH: + if (dpld->dpram->setup_speed) { + dpld->dpram->setup_speed(DPRAM_SPEED_HIGH); + resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_HIGH); + send_int2cp(dpld, resp); + } + break; - for (i = 0; i < dpld->max_ipc_dev; i++) { - spin_lock_init(&dpld->tx_lock[i]); - atomic_set(&dpld->res_required[i], 0); - skb_queue_purge(&dpld->skb_rxq[i]); + default: + mif_info("%s: unknown command 0x%04X\n", ld->name, cmd); + break; } +} - /* Enable IPC */ - atomic_set(&dpld->accessing, 0); - - set_magic(dpld, DPRAM_MAGIC_CODE); - set_access(dpld, 1); - if (get_magic(dpld) != DPRAM_MAGIC_CODE || get_access(dpld) != 1) - return -EACCES; +/** + * udl_command_handler + * @dpld: pointer to an instance of dpram_link_device structure + * @cmd: DPRAM upload/download command from a CP + * + * Processes a command for upload/download from a CP. + */ +static void udl_command_handler(struct dpram_link_device *dpld, u16 cmd) +{ + struct link_device *ld = &dpld->ld; - ld->mode = LINK_MODE_IPC; + if (cmd & UDL_RESULT_FAIL) { + mif_err("%s: ERR! command fail (0x%04X)\n", ld->name, cmd); + return; + } - if (wake_lock_active(&dpld->wlock)) - wake_unlock(&dpld->wlock); + switch (UDL_CMD_MASK(cmd)) { + case UDL_CMD_RECV_READY: + mif_err("%s: [CP->AP] CMD_DL_READY (0x%04X)\n", ld->name, cmd); +#ifdef CONFIG_CDMA_MODEM_CBP72 + mif_err("%s: [AP->CP] CMD_DL_START_REQ (0x%04X)\n", + ld->name, CMD_DL_START_REQ); + send_int2cp(dpld, CMD_DL_START_REQ); +#else + complete(&dpld->udl_cmpl); +#endif + break; - return 0; + default: + complete(&dpld->udl_cmpl); + } } +/** + * cmd_req_active_handler + * @dpld: pointer to an instance of dpram_link_device structure + * + * Handles the REQ_ACTIVE command from a CP. + */ static void cmd_req_active_handler(struct dpram_link_device *dpld) { - send_intr(dpld, INT_CMD(INT_CMD_RES_ACTIVE)); + send_int2cp(dpld, INT_CMD(INT_CMD_RES_ACTIVE)); } +/** + * cmd_crash_reset_handler + * @dpld: pointer to an instance of dpram_link_device structure + * + * Handles the CRASH_RESET command from a CP. + */ static void cmd_crash_reset_handler(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; struct io_device *iod = NULL; + int i; ld->mode = LINK_MODE_ULOAD; if (!wake_lock_active(&dpld->wlock)) wake_lock(&dpld->wlock); + /* Stop network interfaces */ + mif_netif_stop(ld); + + /* Purge the skb_txq in every IPC device (IPC_FMT, IPC_RAW, etc.) */ + for (i = 0; i < ld->max_ipc_dev; i++) + skb_queue_purge(ld->skb_txq[i]); + mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name); + /* Change the modem state to STATE_CRASH_RESET for the FMT IO device */ iod = link_get_iod_with_format(ld, IPC_FMT); iod->modem_state_changed(iod, STATE_CRASH_RESET); + /* Change the modem state to STATE_CRASH_RESET for the BOOT IO device */ iod = link_get_iod_with_format(ld, IPC_BOOT); iod->modem_state_changed(iod, STATE_CRASH_RESET); } +/** + * cmd_crash_exit_handler + * @dpld: pointer to an instance of dpram_link_device structure + * + * Handles the CRASH_EXIT command from a CP. + */ static void cmd_crash_exit_handler(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; - u32 size = dpld->dpctl->dp_size; - char *dpram_buff = NULL; +#ifdef DEBUG_MODEM_IF + struct trace_data *trd; + u8 *dump; + struct timespec ts; + getnstimeofday(&ts); +#endif ld->mode = LINK_MODE_ULOAD; if (!wake_lock_active(&dpld->wlock)) wake_lock(&dpld->wlock); - mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name); - - if (dpld->dp_type == CP_IDPRAM) - dpram_wake_up(dpld); + del_timer(&dpld->crash_ack_timer); - dpram_buff = kzalloc(size + (MAX_MIF_SEPA_SIZE * 2), GFP_ATOMIC); - if (!dpram_buff) { - mif_err("DPRAM dump failed!!\n"); - } else { - memset(dpram_buff, 0, size + (MAX_MIF_SEPA_SIZE * 2)); - memcpy(dpram_buff, MIF_SEPARATOR_DPRAM, MAX_MIF_SEPA_SIZE); - memcpy(dpram_buff + MAX_MIF_SEPA_SIZE, &size, sizeof(u32)); - dpram_buff += (MAX_MIF_SEPA_SIZE * 2); - dpram_dump_memory(ld, dpram_buff); + dpram_wake_up(dpld); + +#ifdef DEBUG_MODEM_IF + if (!dpld->forced_cp_crash) { + dump = capture_mem_dump(ld, dpld->base, dpld->size); + if (dump) { + trd = trq_get_free_slot(&dpld->trace_list); + memcpy(&trd->ts, &ts, sizeof(struct timespec)); + trd->dev = IPC_DEBUG; + trd->data = dump; + trd->size = dpld->size; + } } - - del_timer(&dpld->crash_ack_timer); +#endif if (dpld->ext_op && dpld->ext_op->crash_log) dpld->ext_op->crash_log(dpld); + mif_err("%s: Recv 0xC9 (CRASH_EXIT)\n", ld->name); + handle_cp_crash(dpld); } -static void cmd_phone_start_handler(struct dpram_link_device *dpld) +/** + * init_dpram_ipc + * @dpld: pointer to an instance of dpram_link_device structure + * + * Initializes IPC via DPRAM. + */ +static int init_dpram_ipc(struct dpram_link_device *dpld) { struct link_device *ld = &dpld->ld; - struct io_device *iod = NULL; + int i; + + if (ld->mode == LINK_MODE_IPC && + get_magic(dpld) == DPRAM_MAGIC_CODE && + get_access(dpld) == 1) + mif_info("%s: IPC already initialized\n", ld->name); + + /* Clear pointers in every circular queue */ + for (i = 0; i < ld->max_ipc_dev; i++) { + set_txq_head(dpld, i, 0); + set_txq_tail(dpld, i, 0); + set_rxq_head(dpld, i, 0); + set_rxq_tail(dpld, i, 0); + } + + /* Initialize variables for efficient TX/RX processing */ + for (i = 0; i < ld->max_ipc_dev; i++) + dpld->iod[i] = link_get_iod_with_format(ld, i); + dpld->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW); + + /* Initialize variables for TX flow control */ + for (i = 0; i < ld->max_ipc_dev; i++) + atomic_set(&dpld->res_required[i], 0); + + /* Enable IPC */ + if (wake_lock_active(&dpld->wlock)) + wake_unlock(&dpld->wlock); + + atomic_set(&dpld->accessing, 0); + + set_magic(dpld, DPRAM_MAGIC_CODE); + set_access(dpld, 1); + if (get_magic(dpld) != DPRAM_MAGIC_CODE || get_access(dpld) != 1) + return -EACCES; + + ld->mode = LINK_MODE_IPC; + + return 0; +} + +/** + * reset_dpram_ipc + * @dpld: pointer to an instance of dpram_link_device structure + * + * Reset DPRAM with IPC map. + */ +static void reset_dpram_ipc(struct dpram_link_device *dpld) +{ + int i; + struct link_device *ld = &dpld->ld; + + dpld->set_access(dpld, 0); + + /* Clear pointers in every circular queue */ + for (i = 0; i < ld->max_ipc_dev; i++) { + dpld->set_txq_head(dpld, i, 0); + dpld->set_txq_tail(dpld, i, 0); + dpld->set_rxq_head(dpld, i, 0); + dpld->set_rxq_tail(dpld, i, 0); + } - mif_info("%s: Recv 0xC8 (CP_START)\n", ld->name); + dpld->set_magic(dpld, DPRAM_MAGIC_CODE); + dpld->set_access(dpld, 1); +} + +/** + * cmd_phone_start_handler + * @dpld: pointer to an instance of dpram_link_device structure + * + * Handles the PHONE_START command from a CP. + */ +static void cmd_phone_start_handler(struct dpram_link_device *dpld) +{ + struct link_device *ld = &dpld->ld; + struct io_device *iod; - dpram_init_ipc(dpld); + mif_err("%s: Recv 0xC8 (CP_START)\n", ld->name); iod = link_get_iod_with_format(ld, IPC_FMT); if (!iod) { - mif_info("%s: ERR! no iod\n", ld->name); + mif_err("%s: ERR! no iod\n", ld->name); return; } - if (dpld->ext_op && dpld->ext_op->cp_start_handler) - dpld->ext_op->cp_start_handler(dpld); + init_dpram_ipc(dpld); - if (ld->mc->phone_state != STATE_ONLINE) { - mif_info("%s: phone_state: %d -> ONLINE\n", - ld->name, ld->mc->phone_state); - iod->modem_state_changed(iod, STATE_ONLINE); - } + iod->modem_state_changed(iod, STATE_ONLINE); - mif_info("%s: Send 0xC2 (INIT_END)\n", ld->name); - send_intr(dpld, INT_CMD(INT_CMD_INIT_END)); + if (dpld->ext_op && dpld->ext_op->cp_start_handler) { + dpld->ext_op->cp_start_handler(dpld); + } else { + mif_err("%s: Send 0xC2 (INIT_END)\n", ld->name); + send_int2cp(dpld, INT_CMD(INT_CMD_INIT_END)); + } } -static void command_handler(struct dpram_link_device *dpld, u16 cmd) +/** + * cmd_handler: processes a DPRAM command from a CP + * @dpld: pointer to an instance of dpram_link_device structure + * @cmd: DPRAM command from a CP + */ +static void cmd_handler(struct dpram_link_device *dpld, u16 cmd) { struct link_device *ld = &dpld->ld; @@ -1123,19 +1080,19 @@ static void command_handler(struct dpram_link_device *dpld, u16 cmd) break; case INT_CMD_CRASH_RESET: - dpld->dpram_init_status = DPRAM_INIT_STATE_NONE; + dpld->init_status = DPRAM_INIT_STATE_NONE; cmd_crash_reset_handler(dpld); break; case INT_CMD_CRASH_EXIT: - dpld->dpram_init_status = DPRAM_INIT_STATE_NONE; + dpld->init_status = DPRAM_INIT_STATE_NONE; cmd_crash_exit_handler(dpld); break; case INT_CMD_PHONE_START: - dpld->dpram_init_status = DPRAM_INIT_STATE_READY; + dpld->init_status = DPRAM_INIT_STATE_READY; cmd_phone_start_handler(dpld); - complete_all(&dpld->dpram_init_cmd); + complete_all(&ld->init_cmpl); break; case INT_CMD_NV_REBUILDING: @@ -1143,14 +1100,14 @@ static void command_handler(struct dpram_link_device *dpld, u16 cmd) break; case INT_CMD_PIF_INIT_DONE: - complete_all(&dpld->modem_pif_init_done); + complete_all(&ld->pif_cmpl); break; case INT_CMD_SILENT_NV_REBUILDING: mif_info("%s: SILENT_NV_REBUILDING\n", ld->name); break; - case INT_CMD_NORMAL_PWR_OFF: + case INT_CMD_NORMAL_POWER_OFF: /*ToDo:*/ /*kernel_sec_set_cp_ack()*/; break; @@ -1165,72 +1122,437 @@ static void command_handler(struct dpram_link_device *dpld, u16 cmd) } } -static void ext_command_handler(struct dpram_link_device *dpld, u16 cmd) +/** + * ipc_rx_work + * @work: pointer to an instance of the work_struct structure + * + * Invokes the recv method in the io_device instance to perform receiving IPC + * messages from each skb. + */ +static void ipc_rx_work(struct work_struct *work) +{ + struct dpram_link_device *dpld; + struct link_device *ld; + struct io_device *iod; + struct sk_buff *skb; + int i; + + dpld = container_of(work, struct dpram_link_device, rx_dwork.work); + ld = &dpld->ld; + + for (i = 0; i < ld->max_ipc_dev; i++) { + iod = dpld->iod[i]; + while (1) { + skb = skb_dequeue(ld->skb_rxq[i]); + if (!skb) + break; + + if (iod->recv_skb) { + iod->recv_skb(iod, ld, skb); + } else { + iod->recv(iod, ld, skb->data, skb->len); + dev_kfree_skb_any(skb); + } + } + } +} + +/** + * get_rxq_rcvd + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * OUT @dcst: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a RXQ, size of the buffer, in & out + * pointer values, size of received data} into the 'stat' instance. + * + * Returns an error code. + */ +static int get_rxq_rcvd(struct dpram_link_device *dpld, int dev, + struct mem_status *mst, struct circ_status *dcst) { struct link_device *ld = &dpld->ld; - u16 resp; - switch (EXT_CMD_MASK(cmd)) { - case EXT_CMD_SET_SPEED_LOW: - if (dpld->dpctl->setup_speed) { - dpld->dpctl->setup_speed(DPRAM_SPEED_LOW); - resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_LOW); - send_intr(dpld, resp); + dcst->buff = get_rxq_buff(dpld, dev); + dcst->qsize = get_rxq_buff_size(dpld, dev); + dcst->in = mst->head[dev][RX]; + dcst->out = mst->tail[dev][RX]; + dcst->size = circ_get_usage(dcst->qsize, dcst->in, dcst->out); + + if (circ_valid(dcst->qsize, dcst->in, dcst->out)) { + mif_debug("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n", + ld->name, get_dev_name(dev), dcst->qsize, dcst->in, + dcst->out, dcst->size); + return 0; + } else { + mif_err("%s: ERR! %s_RXQ invalid (qsize[%d] in[%d] out[%d])\n", + ld->name, get_dev_name(dev), dcst->qsize, dcst->in, + dcst->out); + return -EIO; + } +} + +/** + * rx_sipc4_frames + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Returns + * ret < 0 : error + * ret == 0 : ILLEGAL status + * ret > 0 : valid data + * + * Must be invoked only when there is data in the corresponding RXQ. + * + * Requires a bottom half (e.g. ipc_rx_task) that will invoke the recv method in + * the io_device instance. + */ +static int rx_sipc4_frames(struct dpram_link_device *dpld, int dev, + struct mem_status *mst) +{ + struct link_device *ld = &dpld->ld; + struct sk_buff *skb; + u8 *dst; + struct circ_status dcst; + int rcvd; + + rcvd = get_rxq_rcvd(dpld, dev, mst, &dcst); + if (unlikely(rcvd < 0)) { +#ifdef DEBUG_MODEM_IF + trigger_forced_cp_crash(dpld); +#endif + goto exit; + } + rcvd = dcst.size; + + /* Allocate an skb */ + skb = dev_alloc_skb(rcvd); + if (!skb) { + mif_info("%s: ERR! %s dev_alloc_skb fail\n", + ld->name, get_dev_name(dev)); + rcvd = -ENOMEM; + goto exit; + } + + /* Read data from the RXQ */ + dst = skb_put(skb, rcvd); + circ_read16_from_io(dst, dcst.buff, dcst.qsize, dcst.out, rcvd); + + /* Store the skb to the corresponding skb_rxq */ + skb_queue_tail(ld->skb_rxq[dev], skb); + +exit: + /* Update tail (out) pointer to empty out the RXQ */ + set_rxq_tail(dpld, dev, dcst.in); + + return rcvd; +} + +/** + * rx_sipc5_frames + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Returns + * ret < 0 : error + * ret == 0 : ILLEGAL status + * ret > 0 : valid data + * + * Must be invoked only when there is data in the corresponding RXQ. + * + * Requires a recv_skb method in the io_device instance, so this function must + * be used for only SIPC5. + */ +static int rx_sipc5_frames(struct dpram_link_device *dpld, int dev, + struct mem_status *mst) +{ + struct link_device *ld = &dpld->ld; + struct sk_buff *skb; + /** + * variables for the status of the circular queue + */ + u8 __iomem *src; + u8 hdr[SIPC5_MIN_HEADER_SIZE]; + struct circ_status dcst; + /** + * variables for RX processing + */ + int qsize; /* size of the queue */ + int rcvd; /* size of data in the RXQ or error */ + int rest; /* size of the rest data */ + int idx; /* index to the start of current frame */ + u8 *frm; /* pointer to current frame */ + u8 *dst; /* pointer to the destination buffer */ + int tot; /* total length including padding data */ + /** + * variables for debug logging + */ + struct mif_irq_map map; + + /* Get data size in the RXQ and in/out pointer values */ + rcvd = get_rxq_rcvd(dpld, dev, mst, &dcst); + if (unlikely(rcvd < 0)) { + mif_err("%s: ERR! rcvd %d < 0\n", ld->name, rcvd); + goto exit; + } + + rcvd = dcst.size; + src = dcst.buff; + qsize = dcst.qsize; + idx = dcst.out; + + if (dev == IPC_FMT) { + set_dpram_map(dpld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_recv", sizeof("ipc_recv")); + } + +#if 0 + skb = dev_alloc_skb(rcvd); + + /* + ** If there is enough free space for an skb to store received + ** data at once, + */ + if (skb) { + /* Read all data from the RXQ to the skb */ + dst = skb_put(skb, rcvd); + if (unlikely(dpld->strict_io_access)) + circ_read16_from_io(dst, src, qsize, idx, rcvd); + else + circ_read(dst, src, qsize, idx, rcvd); + +#ifdef DEBUG_MODEM_IF + /* Verify data copied to the skb */ + if (ld->aligned && memcmp16_to_io((src + idx), dst, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + rcvd = -EIO; + goto exit; } - break; +#endif - case EXT_CMD_SET_SPEED_MID: - if (dpld->dpctl->setup_speed) { - dpld->dpctl->setup_speed(DPRAM_SPEED_MID); - resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_MID); - send_intr(dpld, resp); + /* Store the skb to the corresponding skb_rxq */ + skb_queue_tail(ld->skb_rxq[dev], skb); + + goto exit; + } + + /* + ** If there was no enough space to store received data at once, + */ +#endif + + rest = rcvd; + while (rest > 0) { + /* Calculate the start of an SIPC5 frame */ + frm = src + idx; + + /* Copy the header in the frame to the header buffer */ + if (unlikely(dpld->strict_io_access)) + memcpy16_from_io(hdr, frm, SIPC5_MIN_HEADER_SIZE); + else + memcpy(hdr, frm, SIPC5_MIN_HEADER_SIZE); + + /* Check the config field in the header */ + if (unlikely(!sipc5_start_valid(hdr))) { + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: BAD CONFIG", + ld->mc->name); + mif_err("%s: ERR! %s INVALID config 0x%02X\n", + ld->name, get_dev_name(dev), hdr[0]); + pr_ipc(1, str, hdr, 4); + rcvd = -EBADMSG; + goto exit; } - break; - case EXT_CMD_SET_SPEED_HIGH: - if (dpld->dpctl->setup_speed) { - dpld->dpctl->setup_speed(DPRAM_SPEED_HIGH); - resp = INT_EXT_CMD(EXT_CMD_SET_SPEED_HIGH); - send_intr(dpld, resp); + /* Verify the total length of the frame (data + padding) */ + tot = sipc5_get_total_len(hdr); + if (unlikely(tot > rest)) { + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: BAD LENGTH", + ld->mc->name); + mif_err("%s: ERR! %s tot %d > rest %d\n", + ld->name, get_dev_name(dev), tot, rest); + pr_ipc(1, str, hdr, 4); + rcvd = -EBADMSG; +#if defined(CONFIG_MACH_C1_KOR_SKT) || defined(CONFIG_MACH_C1_KOR_KT) || defined(CONFIG_MACH_C1_KOR_LGT) + return rcvd; +#else + goto exit; +#endif } - break; - default: - mif_info("%s: unknown command 0x%04X\n", ld->name, cmd); - break; + /* Allocate an skb */ + skb = dev_alloc_skb(tot); + if (!skb) { + mif_err("%s: ERR! %s dev_alloc_skb fail\n", + ld->name, get_dev_name(dev)); + rcvd = -ENOMEM; + goto exit; + } + + /* Set the attribute of the skb as "single frame" */ + skbpriv(skb)->single_frame = true; + + /* Read the frame from the RXQ */ + dst = skb_put(skb, tot); + if (unlikely(dpld->strict_io_access)) + circ_read16_from_io(dst, src, qsize, idx, tot); + else + circ_read(dst, src, qsize, idx, tot); + +#ifdef DEBUG_MODEM_IF + /* Take a log for debugging */ + if (unlikely(dev == IPC_FMT)) { + size_t len = (skb->len > 32) ? 32 : skb->len; + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: CP2MIF", + ld->mc->name); + pr_ipc(0, str, skb->data, len); + } +#endif + +#ifdef DEBUG_MODEM_IF + /* Verify data copied to the skb */ + if (ld->aligned && memcmp16_to_io((src + idx), dst, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + rcvd = -EIO; + goto exit; + } +#endif + + /* Store the skb to the corresponding skb_rxq */ + skb_queue_tail(ld->skb_rxq[dev], skb); + + /* Calculate new idx value */ + rest -= tot; + idx += tot; + if (unlikely(idx >= qsize)) + idx -= qsize; } + +exit: +#ifdef DEBUG_MODEM_IF + if (rcvd < 0) + trigger_forced_cp_crash(dpld); +#endif + + /* Update tail (out) pointer to empty out the RXQ */ + set_rxq_tail(dpld, dev, dcst.in); + + return rcvd; } -static void udl_command_handler(struct dpram_link_device *dpld, u16 cmd) +/** + * msg_handler: receives IPC messages from every RXQ + * @dpld: pointer to an instance of dpram_link_device structure + * @stat: pointer to an instance of mem_status structure + * + * 1) Receives all IPC message frames currently in every DPRAM RXQ. + * 2) Sends RES_ACK responses if there are REQ_ACK requests from a CP. + * 3) Completes all threads waiting for the corresponding RES_ACK from a CP if + * there is any RES_ACK response. + */ +static void msg_handler(struct dpram_link_device *dpld, struct mem_status *stat) { struct link_device *ld = &dpld->ld; + int i = 0; + int ret = 0; + u16 mask = 0; + u16 intr = stat->int2ap; - if (cmd & UDL_RESULT_FAIL) { - mif_info("%s: ERR! Command failed: %04x\n", ld->name, cmd); + if (!ipc_active(dpld)) return; + + /* Read data from DPRAM */ + for (i = 0; i < ld->max_ipc_dev; i++) { + /* Invoke an RX function only when there is data in the RXQ */ + if (unlikely(stat->head[i][RX] == stat->tail[i][RX])) { + mif_debug("%s: %s_RXQ is empty\n", + ld->name, get_dev_name(i)); + } else { + if (unlikely(ld->ipc_version < SIPC_VER_50)) + ret = rx_sipc4_frames(dpld, i, stat); + else + ret = rx_sipc5_frames(dpld, i, stat); + if (ret < 0) + reset_rxq_circ(dpld, i); + } } - switch (UDL_CMD_MASK(cmd)) { - case UDL_CMD_RECV_READY: - mif_debug("%s: Send CP-->AP RECEIVE_READY\n", ld->name); - send_intr(dpld, CMD_IMG_START_REQ); - break; - default: - complete_all(&dpld->udl_cmd_complete); + /* Schedule soft IRQ for RX */ + queue_delayed_work(system_nrt_wq, &dpld->rx_dwork, 0); + + /* Check and process REQ_ACK (at this time, in == out) */ + if (unlikely(intr & INT_MASK_REQ_ACK_SET)) { + for (i = 0; i < ld->max_ipc_dev; i++) { + if (intr & get_mask_req_ack(dpld, i)) { + mif_debug("%s: set %s_RES_ACK\n", + ld->name, get_dev_name(i)); + mask |= get_mask_res_ack(dpld, i); + } + } + + send_int2cp(dpld, INT_NON_CMD(mask)); + } + + /* Check and process RES_ACK */ + if (unlikely(intr & INT_MASK_RES_ACK_SET)) { + for (i = 0; i < ld->max_ipc_dev; i++) { + if (intr & get_mask_res_ack(dpld, i)) { +#ifdef DEBUG_MODEM_IF + mif_info("%s: recv %s_RES_ACK\n", + ld->name, get_dev_name(i)); + print_circ_status(ld, i, stat); +#endif + complete(&dpld->req_ack_cmpl[i]); + } + } } } -static inline void dpram_ipc_rx(struct dpram_link_device *dpld, u16 intr) +/** + * cmd_msg_handler: processes a DPRAM command or receives IPC messages + * @dpld: pointer to an instance of dpram_link_device structure + * @stat: pointer to an instance of mem_status structure + * + * Invokes cmd_handler for a DPRAM command or msg_handler for IPC messages. + */ +static inline void cmd_msg_handler(struct dpram_link_device *dpld, + struct mem_status *stat) { - if (unlikely(INT_CMD_VALID(intr))) - command_handler(dpld, intr); - else - non_command_handler(dpld, intr); + struct dpram_ext_op *ext_op = dpld->ext_op; + struct mem_status *mst = msq_get_free_slot(&dpld->stat_list); + u16 intr = stat->int2ap; + + memcpy(mst, stat, sizeof(struct mem_status)); + + if (unlikely(INT_CMD_VALID(intr))) { + if (ext_op && ext_op->cmd_handler) + ext_op->cmd_handler(dpld, intr); + else + cmd_handler(dpld, intr); + } else { + msg_handler(dpld, stat); + } } -static inline void dpram_intr_handler(struct dpram_link_device *dpld, u16 intr) +/** + * intr_handler: processes an interrupt from a CP + * @dpld: pointer to an instance of dpram_link_device structure + * @stat: pointer to an instance of mem_status structure + * + * Call flow for normal interrupt handling: + * cmd_msg_handler -> cmd_handler -> cmd_xxx_handler + * cmd_msg_handler -> msg_handler -> rx_sipc5_frames -> ... + */ +static inline void intr_handler(struct dpram_link_device *dpld, + struct mem_status *stat) { char *name = dpld->ld.name; + u16 intr = stat->int2ap; if (unlikely(intr == INT_POWERSAFE_FAIL)) { mif_info("%s: intr == INT_POWERSAFE_FAIL\n", name); @@ -1247,131 +1569,696 @@ static inline void dpram_intr_handler(struct dpram_link_device *dpld, u16 intr) mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); } else { - mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); + mif_err("%s: ERR! invalid intr 0x%04X\n", name, intr); } + return; } if (likely(INT_VALID(intr))) - dpram_ipc_rx(dpld, intr); + cmd_msg_handler(dpld, stat); else - mif_info("%s: ERR! invalid intr 0x%04X\n", name, intr); + mif_err("%s: ERR! invalid intr 0x%04X\n", name, intr); } +/** + * ap_idpram_irq_handler: interrupt handler for an internal DPRAM in an AP + * @irq: IRQ number + * @data: pointer to a data + * + * 1) Reads the interrupt value + * 2) Performs interrupt handling + */ static irqreturn_t ap_idpram_irq_handler(int irq, void *data) { struct dpram_link_device *dpld = (struct dpram_link_device *)data; struct link_device *ld = (struct link_device *)&dpld->ld; - u16 int2ap = recv_intr(dpld); + struct modemlink_dpram_data *dpram = dpld->dpram; + struct mem_status stat; if (unlikely(ld->mode == LINK_MODE_OFFLINE)) return IRQ_HANDLED; - dpram_intr_handler(dpld, int2ap); + get_dpram_status(dpld, RX, &stat); + + intr_handler(dpld, &stat); + + if (likely(dpram->clear_int2ap)) + dpram->clear_int2ap(); return IRQ_HANDLED; } +/** + * cp_idpram_irq_handler: interrupt handler for an internal DPRAM in a CP + * @irq: IRQ number + * @data: pointer to a data + * + * 1) Wakes up the DPRAM + * 2) Reads the interrupt value + * 3) Performs interrupt handling + * 4) Clears the interrupt port (port = memory or register) + * 5) Allows the DPRAM to sleep + */ static irqreturn_t cp_idpram_irq_handler(int irq, void *data) { struct dpram_link_device *dpld = (struct dpram_link_device *)data; struct link_device *ld = (struct link_device *)&dpld->ld; - u16 int2ap; - - if (unlikely(ld->mode == LINK_MODE_OFFLINE)) + struct dpram_ext_op *ext_op = dpld->ext_op; + struct mem_status stat; + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) { + mif_err("%s: ERR! ld->mode == LINK_MODE_OFFLINE\n", ld->name); + get_dpram_status(dpld, RX, &stat); +#ifdef DEBUG_MODEM_IF + print_mem_status(ld, &stat); +#endif return IRQ_HANDLED; + } if (dpram_wake_up(dpld) < 0) { - log_dpram_status(dpld); - trigger_force_cp_crash(dpld); + trigger_forced_cp_crash(dpld); return IRQ_HANDLED; } - int2ap = recv_intr(dpld); + get_dpram_status(dpld, RX, &stat); - dpram_intr_handler(dpld, int2ap); + intr_handler(dpld, &stat); - clear_intr(dpld); + if (likely(ext_op && ext_op->clear_int2ap)) + ext_op->clear_int2ap(dpld); dpram_allow_sleep(dpld); return IRQ_HANDLED; } +/** + * ext_dpram_irq_handler: interrupt handler for a normal external DPRAM + * @irq: IRQ number + * @data: pointer to a data + * + * 1) Reads the interrupt value + * 2) Performs interrupt handling + */ static irqreturn_t ext_dpram_irq_handler(int irq, void *data) { struct dpram_link_device *dpld = (struct dpram_link_device *)data; struct link_device *ld = (struct link_device *)&dpld->ld; - u16 int2ap = recv_intr(dpld); + struct mem_status stat; if (unlikely(ld->mode == LINK_MODE_OFFLINE)) return IRQ_HANDLED; - dpram_intr_handler(dpld, int2ap); + get_dpram_status(dpld, RX, &stat); + + intr_handler(dpld, &stat); return IRQ_HANDLED; } -static void dpram_send_ipc(struct link_device *ld, int dev, - struct io_device *iod, struct sk_buff *skb) +/** + * get_txq_space + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * OUT @stat: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a TXQ, size of the buffer, in & out + * pointer values, size of free space} into the 'stat' instance. + * + * Returns the size of free space in the buffer or an error code. + */ +static int get_txq_space(struct dpram_link_device *dpld, int dev, + struct circ_status *stat) { - struct dpram_link_device *dpld = to_dpram_link_device(ld); + struct link_device *ld = &dpld->ld; + int cnt = 0; + u32 qsize; + u32 head; + u32 tail; + int space; + + while (1) { + qsize = get_txq_buff_size(dpld, dev); + head = get_txq_head(dpld, dev); + tail = get_txq_tail(dpld, dev); + space = circ_get_space(qsize, head, tail); + + mif_debug("%s: %s_TXQ{qsize:%u in:%u out:%u space:%u}\n", + ld->name, get_dev_name(dev), qsize, head, tail, space); + + if (circ_valid(qsize, head, tail)) + break; + + cnt++; + mif_err("%s: ERR! invalid %s_TXQ{qsize:%d in:%d out:%d " + "space:%d}, count %d\n", + ld->name, get_dev_name(dev), qsize, head, tail, + space, cnt); + if (cnt >= MAX_RETRY_CNT) { + space = -EIO; + break; + } + + udelay(100); + } + + stat->buff = get_txq_buff(dpld, dev); + stat->qsize = qsize; + stat->in = head; + stat->out = tail; + stat->size = space; + + return space; +} + +/** + * write_ipc_to_txq + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @stat: pointer to an instance of circ_status structure + * @skb: pointer to an instance of sk_buff structure + * + * Must be invoked only when there is enough space in the TXQ. + */ +static void write_ipc_to_txq(struct dpram_link_device *dpld, int dev, + struct circ_status *stat, struct sk_buff *skb) +{ + struct link_device *ld = &dpld->ld; + u8 __iomem *buff = stat->buff; + u32 qsize = stat->qsize; + u32 in = stat->in; + u8 *src = skb->data; + u32 len = skb->len; + struct mif_irq_map map; + + /* Write data to the TXQ */ + if (unlikely(dpld->strict_io_access)) + circ_write16_to_io(buff, src, qsize, in, len); + else + circ_write(buff, src, qsize, in, len); + + /* Update new head (in) pointer */ + set_txq_head(dpld, dev, circ_new_pointer(qsize, in, len)); + + /* Take a log for debugging */ + if (dev == IPC_FMT) { +#ifdef DEBUG_MODEM_IF + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: MIF2CP", ld->mc->name); + pr_ipc(0, tag, src, (len > 32 ? 32 : len)); +#endif + set_dpram_map(dpld, &map); + mif_irq_log(ld->mc->msd, map, "ipc_write", sizeof("ipc_write")); + mif_ipc_log(MIF_IPC_AP2CP, ld->mc->msd, skb->data, skb->len); + } + +#ifdef DEBUG_MODEM_IF + /* Verify data written to the TXQ */ + if (ld->aligned && memcmp16_to_io((buff + in), src, 4)) { + mif_err("%s: memcmp16_to_io fail\n", ld->name); + trigger_forced_cp_crash(dpld); + } +#endif +} + +/** + * xmit_ipc_msg + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Tries to transmit IPC messages in the skb_txq of @dev as many as possible. + * + * Returns total length of IPC messages transmitted or an error code. + */ +static int xmit_ipc_msg(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; struct sk_buff_head *txq = ld->skb_txq[dev]; + struct sk_buff *skb; + unsigned long flags; + struct circ_status stat; + int space; + int copied = 0; + + /* Acquire the spin lock for a TXQ */ + spin_lock_irqsave(&dpld->tx_lock[dev], flags); + + while (1) { + /* Get the size of free space in the TXQ */ + space = get_txq_space(dpld, dev, &stat); + if (unlikely(space < 0)) { +#ifdef DEBUG_MODEM_IF + /* Trigger a enforced CP crash */ + trigger_forced_cp_crash(dpld); +#endif + /* Empty out the TXQ */ + reset_txq_circ(dpld, dev); + copied = -EIO; + break; + } + + skb = skb_dequeue(txq); + if (unlikely(!skb)) + break; + + /* Check the free space size comparing with skb->len */ + if (unlikely(space < skb->len)) { +#ifdef DEBUG_MODEM_IF + struct mem_status mst; +#endif + /* Set res_required flag for the "dev" */ + atomic_set(&dpld->res_required[dev], 1); + + /* Take the skb back to the skb_txq */ + skb_queue_head(txq, skb); + + mif_info("%s: <called by %pf> NOSPC in %s_TXQ" + "{qsize:%u in:%u out:%u}, free:%u < len:%u\n", + ld->name, CALLER, get_dev_name(dev), + stat.qsize, stat.in, stat.out, space, skb->len); +#ifdef DEBUG_MODEM_IF + get_dpram_status(dpld, TX, &mst); + print_circ_status(ld, dev, &mst); +#endif + copied = -ENOSPC; + break; + } + + /* TX only when there is enough space in the TXQ */ + write_ipc_to_txq(dpld, dev, &stat, skb); + copied += skb->len; + dev_kfree_skb_any(skb); + } + + /* Release the spin lock */ + spin_unlock_irqrestore(&dpld->tx_lock[dev], flags); + + return copied; +} + +/** + * wait_for_res_ack + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * 1) Sends an REQ_ACK interrupt for @dev to CP. + * 2) Waits for the corresponding RES_ACK for @dev from CP. + * + * Returns the return value from wait_for_completion_interruptible_timeout(). + */ +static int wait_for_res_ack(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + struct completion *cmpl = &dpld->req_ack_cmpl[dev]; + unsigned long timeout = msecs_to_jiffies(dpld->res_ack_wait_timeout); int ret; u16 mask; - skb_queue_tail(txq, skb); - if (txq->qlen > 1024) { - mif_debug("%s: %s txq->qlen %d > 1024\n", - ld->name, get_dev_name(dev), txq->qlen); +#ifdef DEBUG_MODEM_IF + mif_info("%s: send %s_REQ_ACK\n", ld->name, get_dev_name(dev)); +#endif + + mask = get_mask_req_ack(dpld, dev); + send_int2cp(dpld, INT_NON_CMD(mask)); + + ret = wait_for_completion_interruptible_timeout(cmpl, timeout); + /* ret == 0 on timeout, ret < 0 if interrupted */ + if (ret < 0) { + mif_info("%s: %s: wait_for_completion interrupted! (ret %d)\n", + ld->name, get_dev_name(dev), ret); + goto exit; } - if (dpld->dp_type == CP_IDPRAM) { - if (dpram_wake_up(dpld) < 0) { - trigger_force_cp_crash(dpld); - return; + if (ret == 0) { + struct mem_status mst; + get_dpram_status(dpld, TX, &mst); + + mif_info("%s: wait_for_completion TIMEOUT! (no %s_RES_ACK)\n", + ld->name, get_dev_name(dev)); + + /* + ** The TXQ must be checked whether or not it is empty, because + ** an interrupt mask can be overwritten by the next interrupt. + */ + if (mst.head[dev][TX] == mst.tail[dev][TX]) { + ret = get_txq_buff_size(dpld, dev); +#ifdef DEBUG_MODEM_IF + mif_info("%s: %s_TXQ has been emptied\n", + ld->name, get_dev_name(dev)); + print_circ_status(ld, dev, &mst); +#endif } } - if (!dpram_ipc_active(dpld)) - goto exit; +exit: + return ret; +} + +/** + * process_res_ack + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * 1) Tries to transmit IPC messages in the skb_txq by invoking xmit_ipc_msg() + * function. + * 2) Sends an interrupt to CP if there is no error from xmit_ipc_msg(). + * 3) Restarts DPRAM flow control if xmit_ipc_msg() returns -ENOSPC. + * + * Returns the return value from xmit_ipc_msg(). + */ +static int process_res_ack(struct dpram_link_device *dpld, int dev) +{ + int ret; + u16 mask; + + ret = xmit_ipc_msg(dpld, dev); + if (ret > 0) { + mask = get_mask_send(dpld, dev); + send_int2cp(dpld, INT_NON_CMD(mask)); + get_dpram_status(dpld, TX, msq_get_free_slot(&dpld->stat_list)); + } + + if (ret >= 0) + atomic_set(&dpld->res_required[dev], 0); + + return ret; +} + +/** + * fmt_tx_work: performs TX for FMT IPC device under DPRAM flow control + * @work: pointer to an instance of the work_struct structure + * + * 1) Starts waiting for RES_ACK of FMT IPC device. + * 2) Returns immediately if the wait is interrupted. + * 3) Restarts DPRAM flow control if there is a timeout from the wait. + * 4) Otherwise, it performs processing RES_ACK for FMT IPC device. + */ +static void fmt_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + unsigned long delay = 0; + int ret; + + ld = container_of(work, struct link_device, fmt_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = wait_for_res_ack(dpld, IPC_FMT); + /* ret < 0 if interrupted */ + if (ret < 0) + return; + + /* ret == 0 on timeout */ + if (ret == 0) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_FMT], 0); + return; + } + + ret = process_res_ack(dpld, IPC_FMT); + if (ret >= 0) { + dpram_allow_sleep(dpld); + return; + } + + /* At this point, ret < 0 */ + if (ret == -ENOSPC) + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_FMT], delay); +} + +/** + * raw_tx_work: performs TX for RAW IPC device under DPRAM flow control. + * @work: pointer to an instance of the work_struct structure + * + * 1) Starts waiting for RES_ACK of RAW IPC device. + * 2) Returns immediately if the wait is interrupted. + * 3) Restarts DPRAM flow control if there is a timeout from the wait. + * 4) Otherwise, it performs processing RES_ACK for RAW IPC device. + */ +static void raw_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + unsigned long delay = 0; + int ret; + + ld = container_of(work, struct link_device, raw_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = wait_for_res_ack(dpld, IPC_RAW); + /* ret < 0 if interrupted */ + if (ret < 0) + return; + + /* ret == 0 on timeout */ + if (ret == 0) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_RAW], 0); + return; + } + + ret = process_res_ack(dpld, IPC_RAW); + if (ret >= 0) { + dpram_allow_sleep(dpld); + mif_netif_wake(ld); + return; + } + + /* At this point, ret < 0 */ + if (ret == -ENOSPC) + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_RAW], delay); +} + +/** + * rfs_tx_work: performs TX for RFS IPC device under DPRAM flow control + * @work: pointer to an instance of the work_struct structure + * + * 1) Starts waiting for RES_ACK of RFS IPC device. + * 2) Returns immediately if the wait is interrupted. + * 3) Restarts DPRAM flow control if there is a timeout from the wait. + * 4) Otherwise, it performs processing RES_ACK for RFS IPC device. + */ +static void rfs_tx_work(struct work_struct *work) +{ + struct link_device *ld; + struct dpram_link_device *dpld; + unsigned long delay = 0; + int ret; + + ld = container_of(work, struct link_device, rfs_tx_dwork.work); + dpld = to_dpram_link_device(ld); + + ret = wait_for_res_ack(dpld, IPC_RFS); + /* ret < 0 if interrupted */ + if (ret < 0) + return; + + /* ret == 0 on timeout */ + if (ret == 0) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_RFS], 0); + return; + } + + ret = process_res_ack(dpld, IPC_RFS); + if (ret >= 0) { + dpram_allow_sleep(dpld); + return; + } + + /* At this point, ret < 0 */ + if (ret == -ENOSPC) + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_RFS], delay); +} + +/** + * dpram_send_ipc + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * 1) Tries to transmit IPC messages in the skb_txq by invoking xmit_ipc_msg() + * function. + * 2) Sends an interrupt to CP if there is no error from xmit_ipc_msg(). + * 3) Starts DPRAM flow control if xmit_ipc_msg() returns -ENOSPC. + */ +static int dpram_send_ipc(struct dpram_link_device *dpld, int dev) +{ + struct link_device *ld = &dpld->ld; + int ret; + u16 mask; if (atomic_read(&dpld->res_required[dev]) > 0) { - mif_debug("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev)); + mif_info("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev)); + return 0; + } + + if (dpram_wake_up(dpld) < 0) { + trigger_forced_cp_crash(dpld); + return -EIO; + } + + if (!ipc_active(dpld)) { + mif_info("%s: IPC is NOT active\n", ld->name); + ret = -EIO; goto exit; } - ret = dpram_try_ipc_tx(dpld, dev); - if (ret > 0) { + ret = xmit_ipc_msg(dpld, dev); + if (likely(ret > 0)) { mask = get_mask_send(dpld, dev); - send_intr(dpld, INT_NON_CMD(mask)); - } else if (ret == -ENOSPC) { - mask = get_mask_req_ack(dpld, dev); - send_intr(dpld, INT_NON_CMD(mask)); - mif_info("%s: Send REQ_ACK 0x%04X\n", ld->name, mask); - } else { - mif_info("%s: dpram_try_ipc_tx fail (err %d)\n", ld->name, ret); + send_int2cp(dpld, INT_NON_CMD(mask)); + get_dpram_status(dpld, TX, msq_get_free_slot(&dpld->stat_list)); + goto exit; + } + + /* If there was no TX, just exit */ + if (ret == 0) + goto exit; + + /* At this point, ret < 0 */ + if (ret == -ENOSPC) { + /* Prohibit DPRAM from sleeping until the TXQ buffer is empty */ + if (dpram_wake_up(dpld) < 0) { + trigger_forced_cp_crash(dpld); + goto exit; + } + + /*----------------------------------------------------*/ + /* dpld->res_required[dev] was set in xmit_ipc_msg(). */ + /*----------------------------------------------------*/ + + if (dev == IPC_RAW) + mif_netif_stop(ld); + + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], 0); } exit: - if (dpld->dp_type == CP_IDPRAM) - dpram_allow_sleep(dpld); + dpram_allow_sleep(dpld); + return ret; +} + +/** + * pm_tx_work: performs TX while DPRAM PM is locked + * @work: pointer to an instance of the work_struct structure + */ +static void pm_tx_work(struct work_struct *work) +{ + struct idpram_pm_data *pm_data; + struct idpram_pm_op *pm_op; + struct dpram_link_device *dpld; + struct link_device *ld; + struct workqueue_struct *pm_wq = system_nrt_wq; + int i; + int ret; + unsigned long delay = 0; + + pm_data = container_of(work, struct idpram_pm_data, tx_dwork.work); + dpld = container_of(pm_data, struct dpram_link_device, pm_data); + ld = &dpld->ld; + pm_op = dpld->pm_op; + + if (pm_op->locked(dpld)) { + queue_delayed_work(pm_wq, &pm_data->tx_dwork, delay); + return; + } + + /* Here, PM is not locked. */ + for (i = 0; i < ld->max_ipc_dev; i++) { + ret = dpram_send_ipc(dpld, i); + if (ret < 0) { + struct io_device *iod = dpld->iod[i]; + mif_err("%s->%s: ERR! dpram_send_ipc fail (err %d)\n", + iod->name, ld->name, ret); + } + } +} + +/** + * dpram_try_send_ipc + * @dpld: pointer to an instance of dpram_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * 1) Enqueues an skb to the skb_txq for @dev in the link device instance. + * 2) Tries to transmit IPC messages in the skb_txq by invoking xmit_ipc_msg() + * function. + * 3) Sends an interrupt to CP if there is no error from xmit_ipc_msg(). + * 4) Starts DPRAM flow control if xmit_ipc_msg() returns -ENOSPC. + */ +static void dpram_try_send_ipc(struct dpram_link_device *dpld, int dev, + struct io_device *iod, struct sk_buff *skb) +{ + struct link_device *ld = &dpld->ld; + struct idpram_pm_data *pm_data = &dpld->pm_data; + struct idpram_pm_op *pm_op = dpld->pm_op; + struct workqueue_struct *pm_wq = system_nrt_wq; + unsigned long delay = msecs_to_jiffies(10); + struct sk_buff_head *txq = ld->skb_txq[dev]; + int ret; + + if (unlikely(txq->qlen >= MAX_SKB_TXQ_DEPTH)) { + mif_info("%s: %s txq->qlen %d >= %d\n", ld->name, + get_dev_name(dev), txq->qlen, MAX_SKB_TXQ_DEPTH); + dev_kfree_skb_any(skb); + return; + } + + skb_queue_tail(txq, skb); + + if (pm_op && pm_op->locked) { + if (pm_op->locked(dpld)) { + queue_delayed_work(pm_wq, &pm_data->tx_dwork, delay); + return; + } + + /* Here, PM is not locked. */ + if (work_pending(&pm_data->tx_dwork.work)) + cancel_delayed_work_sync(&pm_data->tx_dwork); + } + + ret = dpram_send_ipc(dpld, dev); + if (ret < 0) { + mif_err("%s->%s: ERR! dpram_send_ipc fail (err %d)\n", + iod->name, ld->name, ret); + } } static int dpram_send_cp_binary(struct link_device *ld, struct sk_buff *skb) { struct dpram_link_device *dpld = to_dpram_link_device(ld); - if (dpld->ext_op && dpld->ext_op->download_binary) - return dpld->ext_op->download_binary(dpld, skb); + if (dpld->ext_op && dpld->ext_op->xmit_binary) + return dpld->ext_op->xmit_binary(dpld, skb); else return -ENODEV; } +/** + * dpram_send + * @ld: pointer to an instance of the link_device structure + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * Returns the length of data transmitted or an error code. + * + * Normal call flow for an IPC message: + * dpram_try_send_ipc -> dpram_send_ipc -> xmit_ipc_msg -> write_ipc_to_txq + * + * Call flow on PM lock in a DPRAM IPC TXQ: + * dpram_try_send_ipc ,,, queue_delayed_work + * => pm_tx_work -> dpram_send_ipc -> xmit_ipc_msg -> write_ipc_to_txq + * + * Call flow on congestion in a DPRAM IPC TXQ: + * dpram_try_send_ipc -> xmit_ipc_msg ,,, queue_delayed_work + * => xxx_tx_work -> wait_for_res_ack + * => msg_handler + * => process_res_ack -> xmit_ipc_msg (,,, queue_delayed_work ...) + */ static int dpram_send(struct link_device *ld, struct io_device *iod, - struct sk_buff *skb) + struct sk_buff *skb) { - enum dev_format dev = iod->format; + struct dpram_link_device *dpld = to_dpram_link_device(ld); + int dev = iod->format; int len = skb->len; switch (dev) { @@ -1379,7 +2266,7 @@ static int dpram_send(struct link_device *ld, struct io_device *iod, case IPC_RAW: case IPC_RFS: if (likely(ld->mode == LINK_MODE_IPC)) { - dpram_send_ipc(ld, dev, iod, skb); + dpram_try_send_ipc(dpld, dev, iod, skb); } else { mif_info("%s: ld->mode != LINK_MODE_IPC\n", ld->name); dev_kfree_skb_any(skb); @@ -1396,23 +2283,45 @@ static int dpram_send(struct link_device *ld, struct io_device *iod, } } -static int dpram_force_dump(struct link_device *ld, struct io_device *iod) +static int dpram_xmit_boot(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + if (dpld->ext_op && dpld->ext_op->xmit_boot) + return dpld->ext_op->xmit_boot(dpld, arg); + else + return -ENODEV; +} + +static int dpram_set_dload_magic(struct link_device *ld, struct io_device *iod) { struct dpram_link_device *dpld = to_dpram_link_device(ld); - trigger_force_cp_crash(dpld); + + ld->mode = LINK_MODE_DLOAD; + + mif_err("%s: magic = 0x%08X\n", ld->name, DP_MAGIC_DMDL); + iowrite32(DP_MAGIC_DMDL, dpld->dl_map.magic); + return 0; } -static void dpram_dump_memory(struct link_device *ld, char *buff) +static int dpram_dload_firmware(struct link_device *ld, struct io_device *iod, + unsigned long arg) { struct dpram_link_device *dpld = to_dpram_link_device(ld); - u8 __iomem *base = dpld->dpctl->dp_base; - u32 size = dpld->dpctl->dp_size; - if (dpld->dp_type == CP_IDPRAM) - dpram_wake_up(dpld); + if (dpld->ext_op && dpld->ext_op->firm_update) + return dpld->ext_op->firm_update(dpld, arg); + else + return -ENODEV; +} - memcpy(buff, base, size); +static int dpram_force_dump(struct link_device *ld, struct io_device *iod) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + trigger_forced_cp_crash(dpld); + return 0; } static int dpram_dump_start(struct link_device *ld, struct io_device *iod) @@ -1431,13 +2340,24 @@ static int dpram_dump_update(struct link_device *ld, struct io_device *iod, struct dpram_link_device *dpld = to_dpram_link_device(ld); if (dpld->ext_op && dpld->ext_op->dump_update) - return dpld->ext_op->dump_update(dpld, (void *)arg); + return dpld->ext_op->dump_update(dpld, arg); + else + return -ENODEV; +} + +static int dpram_dump_finish(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct dpram_link_device *dpld = to_dpram_link_device(ld); + + if (dpld->ext_op && dpld->ext_op->dump_finish) + return dpld->ext_op->dump_finish(dpld, arg); else return -ENODEV; } static int dpram_ioctl(struct link_device *ld, struct io_device *iod, - unsigned int cmd, unsigned long arg) + unsigned int cmd, unsigned long arg) { struct dpram_link_device *dpld = to_dpram_link_device(ld); int err = 0; @@ -1447,17 +2367,21 @@ static int dpram_ioctl(struct link_device *ld, struct io_device *iod, switch (cmd) { case IOCTL_DPRAM_INIT_STATUS: mif_debug("%s: get dpram init status\n", ld->name); - return dpld->dpram_init_status; + return dpld->init_status; - default: - if (dpld->ext_ioctl) { - err = dpld->ext_ioctl(dpld, iod, cmd, arg); - } else { - mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); - err = -EINVAL; - } + case IOCTL_MIF_DPRAM_DUMP: + if (copy_to_user((void __user *)arg, &dpld->size, sizeof(u32))) + return -EFAULT; + capture_dpram_snapshot(ld, iod); break; + + default: + if (dpld->ext_ioctl) + return dpld->ext_ioctl(dpld, iod, cmd, arg); + + mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd); + return -EINVAL; } return err; @@ -1468,9 +2392,9 @@ static void dpram_remap_std_16k_region(struct dpram_link_device *dpld) struct dpram_ipc_16k_map *dpram_map; struct dpram_ipc_device *dev; - dpram_map = (struct dpram_ipc_16k_map *)dpld->dp_base; + dpram_map = (struct dpram_ipc_16k_map *)dpld->base; - /* magic code and access enable fields */ + /* "magic code" and "access enable" fields */ dpld->ipc_map.magic = (u16 __iomem *)&dpram_map->magic; dpld->ipc_map.access = (u16 __iomem *)&dpram_map->access; @@ -1519,167 +2443,201 @@ static void dpram_remap_std_16k_region(struct dpram_link_device *dpld) dpld->ipc_map.mbx_ap2cp = (u16 __iomem *)&dpram_map->mbx_ap2cp; } -static int dpram_table_init(struct dpram_link_device *dpld) +static int dpram_init_boot_map(struct dpram_link_device *dpld) { - struct link_device *ld = &dpld->ld; - u8 __iomem *dp_base; - int i; + u8 __iomem *dp_base = dpld->base; + u32 magic_size = DP_DLOAD_MAGIC_SIZE; + u32 mbx_size = DP_MBX_SET_SIZE; - if (!dpld->dp_base) { - mif_info("%s: ERR! dpld->dp_base == NULL\n", ld->name); - return -EINVAL; - } - dp_base = dpld->dp_base; - - /* Map for IPC */ - if (dpld->dpctl->ipc_map) { - memcpy(&dpld->ipc_map, dpld->dpctl->ipc_map, - sizeof(struct dpram_ipc_map)); - } else { - if (dpld->dp_size == DPRAM_SIZE_16KB) - dpram_remap_std_16k_region(dpld); - else - return -EINVAL; - } - - dpld->magic = dpld->ipc_map.magic; - dpld->access = dpld->ipc_map.access; - for (i = 0; i < dpld->max_ipc_dev; i++) - dpld->dev[i] = &dpld->ipc_map.dev[i]; - dpld->mbx2ap = dpld->ipc_map.mbx_cp2ap; - dpld->mbx2cp = dpld->ipc_map.mbx_ap2cp; - - /* Map for booting */ if (dpld->ext_op && dpld->ext_op->init_boot_map) { dpld->ext_op->init_boot_map(dpld); } else { dpld->bt_map.magic = (u32 *)(dp_base); - dpld->bt_map.buff = (u8 *)(dp_base + DP_BOOT_BUFF_OFFSET); - dpld->bt_map.size = dpld->dp_size - 8; + dpld->bt_map.buff = (u8 *)(dp_base + magic_size); + dpld->bt_map.space = dpld->size - (magic_size + mbx_size); } - /* Map for download (FOTA, UDL, etc.) */ + return 0; +} + +static int dpram_init_dload_map(struct dpram_link_device *dpld) +{ + u8 __iomem *dp_base = dpld->base; + u32 magic_size = DP_DLOAD_MAGIC_SIZE; + u32 mbx_size = DP_MBX_SET_SIZE; + if (dpld->ext_op && dpld->ext_op->init_dl_map) { dpld->ext_op->init_dl_map(dpld); } else { dpld->dl_map.magic = (u32 *)(dp_base); - dpld->dl_map.buff = (u8 *)(dp_base + DP_DLOAD_BUFF_OFFSET); + dpld->dl_map.buff = (u8 *)(dp_base + magic_size); + dpld->dl_map.space = dpld->size - (magic_size + mbx_size); } - /* Map for upload mode */ + return 0; +} + +static int dpram_init_uload_map(struct dpram_link_device *dpld) +{ + u8 __iomem *dp_base = dpld->base; + u32 magic_size = DP_DLOAD_MAGIC_SIZE; + u32 mbx_size = DP_MBX_SET_SIZE; + if (dpld->ext_op && dpld->ext_op->init_ul_map) { dpld->ext_op->init_ul_map(dpld); } else { dpld->ul_map.magic = (u32 *)(dp_base); dpld->ul_map.buff = (u8 *)(dp_base + DP_ULOAD_BUFF_OFFSET); + dpld->ul_map.space = dpld->size - (magic_size + mbx_size); } return 0; } +static int dpram_init_ipc_map(struct dpram_link_device *dpld) +{ + int i; + struct link_device *ld = &dpld->ld; + + if (dpld->ext_op && dpld->ext_op->init_ipc_map) { + dpld->ext_op->init_ipc_map(dpld); + } else if (dpld->dpram->ipc_map) { + memcpy(&dpld->ipc_map, dpld->dpram->ipc_map, + sizeof(struct dpram_ipc_map)); + } else { + if (dpld->size == DPRAM_SIZE_16KB) + dpram_remap_std_16k_region(dpld); + else + return -EINVAL; + } + + dpld->magic = dpld->ipc_map.magic; + dpld->access = dpld->ipc_map.access; + for (i = 0; i < ld->max_ipc_dev; i++) + dpld->dev[i] = &dpld->ipc_map.dev[i]; + dpld->mbx2ap = dpld->ipc_map.mbx_cp2ap; + dpld->mbx2cp = dpld->ipc_map.mbx_ap2cp; + + return 0; +} + static void dpram_setup_common_op(struct dpram_link_device *dpld) { - dpld->clear_intr = clear_intr; - dpld->recv_intr = recv_intr; - dpld->send_intr = send_intr; + dpld->recv_intr = recv_int2ap; + dpld->send_intr = send_int2cp; dpld->get_magic = get_magic; dpld->set_magic = set_magic; dpld->get_access = get_access; dpld->set_access = set_access; - dpld->get_tx_head = get_tx_head; - dpld->get_tx_tail = get_tx_tail; - dpld->set_tx_head = set_tx_head; - dpld->set_tx_tail = set_tx_tail; - dpld->get_tx_buff = get_tx_buff; - dpld->get_tx_buff_size = get_tx_buff_size; - dpld->get_rx_head = get_rx_head; - dpld->get_rx_tail = get_rx_tail; - dpld->set_rx_head = set_rx_head; - dpld->set_rx_tail = set_rx_tail; - dpld->get_rx_buff = get_rx_buff; - dpld->get_rx_buff_size = get_rx_buff_size; + dpld->get_txq_head = get_txq_head; + dpld->get_txq_tail = get_txq_tail; + dpld->set_txq_head = set_txq_head; + dpld->set_txq_tail = set_txq_tail; + dpld->get_txq_buff = get_txq_buff; + dpld->get_txq_buff_size = get_txq_buff_size; + dpld->get_rxq_head = get_rxq_head; + dpld->get_rxq_tail = get_rxq_tail; + dpld->set_rxq_head = set_rxq_head; + dpld->set_rxq_tail = set_rxq_tail; + dpld->get_rxq_buff = get_rxq_buff; + dpld->get_rxq_buff_size = get_rxq_buff_size; dpld->get_mask_req_ack = get_mask_req_ack; dpld->get_mask_res_ack = get_mask_res_ack; dpld->get_mask_send = get_mask_send; - dpld->ipc_rx_handler = dpram_ipc_rx; -} - -static int dpram_link_init(struct link_device *ld, struct io_device *iod) -{ - return 0; + dpld->get_dpram_status = get_dpram_status; + dpld->ipc_rx_handler = cmd_msg_handler; + dpld->reset_dpram_ipc = reset_dpram_ipc; } static void dpram_link_terminate(struct link_device *ld, struct io_device *iod) { + if (iod->format == IPC_FMT && ld->mode == LINK_MODE_IPC) { + if (!atomic_read(&iod->opened)) { + ld->mode = LINK_MODE_OFFLINE; + mif_err("%s: %s: link mode is changed: IPC->OFFLINE\n", + iod->name, ld->name); + } + } + return; } struct link_device *dpram_create_link_device(struct platform_device *pdev) { - struct modem_data *mdm_data = NULL; struct dpram_link_device *dpld = NULL; struct link_device *ld = NULL; + struct modem_data *modem = NULL; + struct modemlink_dpram_data *dpram = NULL; struct resource *res = NULL; resource_size_t res_size; - struct modemlink_dpram_control *dpctl = NULL; - unsigned long task_data; int ret = 0; int i = 0; - int bsize; - int qsize; - /* Get the platform data */ - mdm_data = (struct modem_data *)pdev->dev.platform_data; - if (!mdm_data) { - mif_info("ERR! mdm_data == NULL\n"); + /* + ** Alloc an instance of dpram_link_device structure + */ + dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL); + if (!dpld) { + mif_err("ERR! kzalloc dpld fail\n"); goto err; } - mif_info("modem = %s\n", mdm_data->name); - mif_info("link device = %s\n", mdm_data->link_name); + ld = &dpld->ld; - if (!mdm_data->dpram_ctl) { - mif_info("ERR! mdm_data->dpram_ctl == NULL\n"); + /* + ** Get the modem (platform) data + */ + modem = (struct modem_data *)pdev->dev.platform_data; + if (!modem) { + mif_err("ERR! modem == NULL\n"); goto err; } - dpctl = mdm_data->dpram_ctl; - - /* Alloc DPRAM link device structure */ - dpld = kzalloc(sizeof(struct dpram_link_device), GFP_KERNEL); - if (!dpld) { - mif_info("ERR! kzalloc dpld fail\n"); + mif_info("modem = %s\n", modem->name); + mif_info("link device = %s\n", modem->link_name); + + /* + ** Retrieve modem data and DPRAM control data from the modem data + */ + ld->mdm_data = modem; + ld->name = modem->link_name; + ld->ipc_version = modem->ipc_version; + + if (!modem->dpram) { + mif_err("ERR! no modem->dpram\n"); goto err; } - ld = &dpld->ld; + dpram = modem->dpram; - /* Retrieve modem data and DPRAM control data from the modem data */ - ld->mdm_data = mdm_data; - ld->name = mdm_data->link_name; - ld->ipc_version = mdm_data->ipc_version; + dpld->dpram = dpram; + dpld->type = dpram->type; + dpld->ap = dpram->ap; + dpld->strict_io_access = dpram->strict_io_access; - /* Retrieve the most basic data for IPC from the modem data */ - dpld->dpctl = dpctl; - dpld->dp_type = dpctl->dp_type; - - if (mdm_data->ipc_version < SIPC_VER_50) { - if (!dpctl->max_ipc_dev) { - mif_info("ERR! no max_ipc_dev\n"); + if (ld->ipc_version < SIPC_VER_50) { + if (!modem->max_ipc_dev) { + mif_err("%s: ERR! no max_ipc_dev\n", ld->name); goto err; } - ld->aligned = dpctl->aligned; - dpld->max_ipc_dev = dpctl->max_ipc_dev; + ld->aligned = dpram->aligned; + ld->max_ipc_dev = modem->max_ipc_dev; } else { ld->aligned = 1; - dpld->max_ipc_dev = MAX_SIPC5_DEV; + ld->max_ipc_dev = MAX_SIPC5_DEV; } - /* Set attributes as a link device */ - ld->init_comm = dpram_link_init; + /* + ** Set attributes as a link device + */ ld->terminate_comm = dpram_link_terminate; ld->send = dpram_send; + ld->xmit_boot = dpram_xmit_boot; + ld->dload_start = dpram_set_dload_magic; + ld->firm_update = dpram_dload_firmware; ld->force_dump = dpram_force_dump; ld->dump_start = dpram_dump_start; ld->dump_update = dpram_dump_update; + ld->dump_finish = dpram_dump_finish; + /* IOCTL extension */ ld->ioctl = dpram_ioctl; INIT_LIST_HEAD(&ld->list); @@ -1691,129 +2649,217 @@ struct link_device *dpram_create_link_device(struct platform_device *pdev) ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q; ld->skb_txq[IPC_RFS] = &ld->sk_rfs_tx_q; - /* Set up function pointers */ + skb_queue_head_init(&ld->sk_fmt_rx_q); + skb_queue_head_init(&ld->sk_raw_rx_q); + skb_queue_head_init(&ld->sk_rfs_rx_q); + ld->skb_rxq[IPC_FMT] = &ld->sk_fmt_rx_q; + ld->skb_rxq[IPC_RAW] = &ld->sk_raw_rx_q; + ld->skb_rxq[IPC_RFS] = &ld->sk_rfs_rx_q; + + init_completion(&ld->init_cmpl); + init_completion(&ld->pif_cmpl); + + /* + ** Set up function pointers + */ dpram_setup_common_op(dpld); - dpld->dpram_dump = dpram_dump_memory; - dpld->ext_op = dpram_get_ext_op(mdm_data->modem_type); - if (dpld->ext_op && dpld->ext_op->ioctl) - dpld->ext_ioctl = dpld->ext_op->ioctl; - - /* Retrieve DPRAM resource */ - if (!dpctl->dp_base) { - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dpld->ext_op = dpram_get_ext_op(modem->modem_type); + if (dpld->ext_op) { + if (dpld->ext_op->ioctl) + dpld->ext_ioctl = dpld->ext_op->ioctl; + + if (dpld->ext_op->wakeup && dpld->ext_op->sleep) + dpld->need_wake_up = true; + } + + /* + ** Retrieve DPRAM resource + */ + if (!dpram->base) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + STR_DPRAM_BASE); if (!res) { - mif_info("%s: ERR! platform_get_resource fail\n", + mif_err("%s: ERR! no DPRAM resource\n", ld->name); + goto err; + } + res_size = resource_size(res); + + dpram->base = ioremap_nocache(res->start, res_size); + if (!dpram->base) { + mif_err("%s: ERR! ioremap_nocache for BASE fail\n", ld->name); goto err; } + dpram->size = res_size; + } + dpld->base = dpram->base; + dpld->size = dpram->size; + + mif_info("%s: type %d, aligned %d, base 0x%08X, size %d\n", + ld->name, dpld->type, ld->aligned, (int)dpld->base, dpld->size); + + /* + ** Retrieve DPRAM SFR resource if exists + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + STR_DPRAM_SFR_BASE); + if (res) { res_size = resource_size(res); + dpld->sfr_base = ioremap_nocache(res->start, res_size); + if (!dpld->sfr_base) { + mif_err("%s: ERR! ioremap_nocache for SFR fail\n", + ld->name); + goto err; + } + } + + /* + ** Initialize DPRAM maps (physical map -> logical map) + */ + ret = dpram_init_boot_map(dpld); + if (ret < 0) { + mif_err("%s: ERR! dpram_init_boot_map fail (err %d)\n", + ld->name, ret); + goto err; + } - dpctl->dp_base = ioremap_nocache(res->start, res_size); - dpctl->dp_size = res_size; + ret = dpram_init_dload_map(dpld); + if (ret < 0) { + mif_err("%s: ERR! dpram_init_dload_map fail (err %d)\n", + ld->name, ret); + goto err; } - dpld->dp_base = dpctl->dp_base; - dpld->dp_size = dpctl->dp_size; - mif_info("%s: dp_type %d, aligned %d, dp_base 0x%08X, dp_size %d\n", - ld->name, dpld->dp_type, ld->aligned, (int)dpld->dp_base, - dpld->dp_size); + ret = dpram_init_uload_map(dpld); + if (ret < 0) { + mif_err("%s: ERR! dpram_init_uload_map fail (err %d)\n", + ld->name, ret); + goto err; + } - /* Initialize DPRAM map (physical map -> logical map) */ - ret = dpram_table_init(dpld); + ret = dpram_init_ipc_map(dpld); if (ret < 0) { - mif_info("%s: ERR! dpram_table_init fail (err %d)\n", + mif_err("%s: ERR! dpram_init_ipc_map fail (err %d)\n", ld->name, ret); goto err; } + if (dpram->res_ack_wait_timeout > 0) + dpld->res_ack_wait_timeout = dpram->res_ack_wait_timeout; + else + dpld->res_ack_wait_timeout = RES_ACK_WAIT_TIMEOUT; + /* Disable IPC */ - set_magic(dpld, 0); - set_access(dpld, 0); - dpld->dpram_init_status = DPRAM_INIT_STATE_NONE; + if (!dpram->disabled) { + set_magic(dpld, 0); + set_access(dpld, 0); + } + dpld->init_status = DPRAM_INIT_STATE_NONE; - /* Initialize locks, completions, and bottom halves */ - snprintf(dpld->wlock_name, DP_MAX_NAME_LEN, "%s_wlock", ld->name); + /* + ** Initialize locks, completions, and bottom halves + */ + snprintf(dpld->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name); wake_lock_init(&dpld->wlock, WAKE_LOCK_SUSPEND, dpld->wlock_name); - init_completion(&dpld->dpram_init_cmd); - init_completion(&dpld->modem_pif_init_done); - init_completion(&dpld->udl_start_complete); - init_completion(&dpld->udl_cmd_complete); - init_completion(&dpld->dump_start_complete); - init_completion(&dpld->dump_recv_done); - - task_data = (unsigned long)dpld; - tasklet_init(&dpld->rx_tsk, dpram_ipc_rx_task, task_data); - - /* Prepare SKB queue head for RX processing */ - for (i = 0; i < dpld->max_ipc_dev; i++) - skb_queue_head_init(&dpld->skb_rxq[i]); - - /* Prepare RXB queue */ - qsize = DPRAM_MAX_RXBQ_SIZE; - for (i = 0; i < dpld->max_ipc_dev; i++) { - bsize = rxbq_get_page_size(get_rx_buff_size(dpld, i)); - dpld->rxbq[i].size = qsize; - dpld->rxbq[i].in = 0; - dpld->rxbq[i].out = 0; - dpld->rxbq[i].rxb = rxbq_create_pool(bsize, qsize); - if (!dpld->rxbq[i].rxb) { - mif_info("%s: ERR! %s rxbq_create_pool fail\n", - ld->name, get_dev_name(i)); - goto err; - } - mif_info("%s: %s rxbq_pool created (bsize:%d, qsize:%d)\n", - ld->name, get_dev_name(i), bsize, qsize); + init_completion(&dpld->udl_cmpl); + init_completion(&dpld->crash_cmpl); + + for (i = 0; i < ld->max_ipc_dev; i++) + init_completion(&dpld->req_ack_cmpl[i]); + + INIT_DELAYED_WORK(&dpld->rx_dwork, ipc_rx_work); + + for (i = 0; i < ld->max_ipc_dev; i++) { + spin_lock_init(&dpld->tx_lock[i]); + atomic_set(&dpld->res_required[i], 0); + } + + ld->tx_wq = create_singlethread_workqueue("dpram_tx_wq"); + if (!ld->tx_wq) { + mif_err("%s: ERR! fail to create tx_wq\n", ld->name); + goto err; } + INIT_DELAYED_WORK(&ld->fmt_tx_dwork, fmt_tx_work); + INIT_DELAYED_WORK(&ld->raw_tx_dwork, raw_tx_work); + INIT_DELAYED_WORK(&ld->rfs_tx_dwork, rfs_tx_work); + ld->tx_dwork[IPC_FMT] = &ld->fmt_tx_dwork; + ld->tx_dwork[IPC_RAW] = &ld->raw_tx_dwork; + ld->tx_dwork[IPC_RFS] = &ld->rfs_tx_dwork; + +#ifdef DEBUG_MODEM_IF + spin_lock_init(&dpld->stat_list.lock); + spin_lock_init(&dpld->trace_list.lock); +#endif /* Prepare a multi-purpose miscellaneous buffer */ - dpld->buff = kzalloc(dpld->dp_size, GFP_KERNEL); + dpld->buff = kzalloc(dpld->size, GFP_KERNEL); if (!dpld->buff) { - mif_info("%s: ERR! kzalloc dpld->buff fail\n", ld->name); + mif_err("%s: ERR! kzalloc dpld->buff fail\n", ld->name); goto err; } - /* Retrieve DPRAM IRQ GPIO# */ - dpld->gpio_dpram_int = mdm_data->gpio_dpram_int; - - /* Retrieve DPRAM IRQ# */ - if (!dpctl->dpram_irq) { - dpctl->dpram_irq = platform_get_irq_byname(pdev, "dpram_irq"); - if (dpctl->dpram_irq < 0) { - mif_info("%s: ERR! platform_get_irq_byname fail\n", - ld->name); + /* + ** Retrieve DPRAM IRQ GPIO#, IRQ#, and IRQ flags + */ + dpld->gpio_int2ap = modem->gpio_ipc_int2ap; + dpld->gpio_cp_status = modem->gpio_cp_status; + dpld->gpio_cp_wakeup = modem->gpio_cp_wakeup; + if (dpram->type == AP_IDPRAM) { + if (!modem->gpio_ipc_int2cp) { + mif_err("%s: ERR! no gpio_ipc_int2cp\n", ld->name); goto err; } + dpld->gpio_int2cp = modem->gpio_ipc_int2cp; } - dpld->irq = dpctl->dpram_irq; - /* Retrieve DPRAM IRQ flags */ - if (!dpctl->dpram_irq_flags) - dpctl->dpram_irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW); - dpld->irq_flags = dpctl->dpram_irq_flags; + dpld->irq = modem->irq_ipc_int2ap; + + if (modem->irqf_ipc_int2ap) + dpld->irq_flags = modem->irqf_ipc_int2ap; + else + dpld->irq_flags = (IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW); + + /* + ** Initialize power management (PM) for AP_IDPRAM + */ + if (dpld->type == AP_IDPRAM) { + dpld->pm_op = idpram_get_pm_op(dpld->ap); + if (!dpld->pm_op) { + mif_err("%s: no pm_op for AP_IDPRAM\n", ld->name); + goto err; + } + + ret = dpld->pm_op->pm_init(dpld, modem, pm_tx_work); + if (ret) { + mif_err("%s: pm_init fail (err %d)\n", ld->name, ret); + goto err; + } + } - /* Register DPRAM interrupt handler */ - snprintf(dpld->irq_name, DP_MAX_NAME_LEN, "%s_irq", ld->name); + /* + ** Register DPRAM interrupt handler + */ + snprintf(dpld->irq_name, MIF_MAX_NAME_LEN, "%s_irq", ld->name); if (dpld->ext_op && dpld->ext_op->irq_handler) dpld->irq_handler = dpld->ext_op->irq_handler; - else if (dpld->dp_type == CP_IDPRAM) + else if (dpld->type == CP_IDPRAM) dpld->irq_handler = cp_idpram_irq_handler; - else if (dpld->dp_type == AP_IDPRAM) + else if (dpld->type == AP_IDPRAM) dpld->irq_handler = ap_idpram_irq_handler; else dpld->irq_handler = ext_dpram_irq_handler; - ret = dpram_register_isr(dpld->irq, dpld->irq_handler, dpld->irq_flags, + ret = mif_register_isr(dpld->irq, dpld->irq_handler, dpld->irq_flags, dpld->irq_name, dpld); if (ret) goto err; - else - return ld; + + return ld; err: if (dpld) { - if (dpld->buff) - kfree(dpld->buff); + kfree(dpld->buff); kfree(dpld); } |