diff options
Diffstat (limited to 'drivers/misc/modem_if/sipc5_io_device.c')
-rw-r--r-- | drivers/misc/modem_if/sipc5_io_device.c | 1561 |
1 files changed, 807 insertions, 754 deletions
diff --git a/drivers/misc/modem_if/sipc5_io_device.c b/drivers/misc/modem_if/sipc5_io_device.c index a9932c1..71596ae 100644 --- a/drivers/misc/modem_if/sipc5_io_device.c +++ b/drivers/misc/modem_if/sipc5_io_device.c @@ -1,5 +1,4 @@ -/* /linux/drivers/misc/modem_if/sipc5_io_device.c - * +/* * Copyright (C) 2010 Samsung Electronics. * * This software is licensed under the terms of the GNU General Public @@ -24,11 +23,9 @@ #include <linux/if_ether.h> #include <linux/etherdevice.h> #include <linux/device.h> +#include <linux/module.h> -#include <linux/platform_data/modem.h> -#ifdef CONFIG_LINK_DEVICE_C2C -#include <linux/platform_data/c2c.h> -#endif +#include "modem.h" #include "modem_prj.h" #include "modem_utils.h" @@ -104,7 +101,7 @@ static void iodev_showtxlink(struct io_device *iod, void *args) struct link_device *ld = get_current_link(iod); if (iod->io_typ == IODEV_NET && IS_CONNECTED(iod, ld)) - *p += sprintf(*p, "%s: %s\n", iod->name, ld->name); + *p += sprintf(*p, "%s<->%s\n", iod->name, ld->name); } static ssize_t show_txlink(struct device *dev, @@ -130,181 +127,107 @@ static ssize_t store_txlink(struct device *dev, static struct device_attribute attr_txlink = __ATTR(txlink, S_IRUGO | S_IWUSR, show_txlink, store_txlink); -/** - * rx_check_frame_cfg - * @cfg: configuration field of a link layer header - * @frm: pointer to the sipc5_frame_data buffer - * - * 1) Checks whether or not an extended field exists - * 2) Calculates the length of a link layer header - * - * Returns the size of a link layer header - * - * Must be invoked only when the configuration field of the link layer header - * is validated with sipc5_start_valid() function - */ -static int rx_check_frame_cfg(u8 cfg, struct sipc5_frame_data *frm) +static int netif_flow_ctrl(struct link_device *ld, struct sk_buff *skb) { - frm->config = cfg; - - if (likely(cfg & SIPC5_PADDING_EXIST)) - frm->padding = true; - - if (unlikely(cfg & SIPC5_EXT_FIELD_EXIST)) { - if (cfg & SIPC5_CTL_FIELD_EXIST) { - frm->ctl_fld = true; - frm->hdr_len = SIPC5_HEADER_SIZE_WITH_CTL_FLD; - } else { - frm->ext_len = true; - frm->hdr_len = SIPC5_HEADER_SIZE_WITH_EXT_LEN; - } + u8 cmd = skb->data[0]; + + if (cmd == FLOW_CTRL_SUSPEND) { + if (ld->suspend_netif_tx) + goto exit; + ld->suspend_netif_tx = true; + mif_netif_stop(ld); + mif_info("%s: FLOW_CTRL_SUSPEND\n", ld->name); + } else if (cmd == FLOW_CTRL_RESUME) { + if (!ld->suspend_netif_tx) + goto exit; + ld->suspend_netif_tx = false; + mif_netif_wake(ld); + mif_info("%s: FLOW_CTRL_RESUME\n", ld->name); } else { - frm->hdr_len = SIPC5_MIN_HEADER_SIZE; + mif_info("%s: ERR! invalid command %02X\n", ld->name, cmd); } - return frm->hdr_len; -} - -/** - * rx_build_meta_data - * @ld: pointer to the link device - * @frm: pointer to the sipc5_frame_data buffer - * - * Fills each field of sipc5_frame_data from a link layer header - * 1) Extracts the channel ID - * 2) Calculates the length of a link layer frame - * 3) Extracts a control field if exists - * 4) Calculates the length of an IPC message packet in the link layer frame - * - */ -static void rx_build_meta_data(struct link_device *ld, - struct sipc5_frame_data *frm) -{ - u16 *sz16 = (u16 *)(frm->hdr + SIPC5_LEN_OFFSET); - u32 *sz32 = (u32 *)(frm->hdr + SIPC5_LEN_OFFSET); - - frm->ch_id = frm->hdr[SIPC5_CH_ID_OFFSET]; - - if (unlikely(frm->ext_len)) - frm->len = *sz32; - else - frm->len = *sz16; - - if (unlikely(frm->ctl_fld)) - frm->control = frm->hdr[SIPC5_CTL_OFFSET]; - - frm->data_len = frm->len - frm->hdr_len; - - mif_debug("%s: FRM ch:%d len:%d ctl:%02X data.len:%d\n", - ld->name, frm->ch_id, frm->len, frm->control, frm->data_len); +exit: + dev_kfree_skb_any(skb); + return 0; } -/** - * tx_build_link_header - * @frm: pointer to the sipc5_frame_data buffer - * @iod: pointer to the IO device - * @ld: pointer to the link device - * @count: length of the data to be transmitted - * - * Builds the meta data for an SIPC5 frame and the link layer header of it - * Returns the link layer header length for an SIPC5 frame or 0 for other frame - */ -static unsigned tx_build_link_header(struct sipc5_frame_data *frm, - struct io_device *iod, struct link_device *ld, ssize_t count) +static inline int queue_skb_to_iod(struct sk_buff *skb, struct io_device *iod) { - u8 *buff = frm->hdr; - u16 *sz16 = (u16 *)(buff + SIPC5_LEN_OFFSET); - u32 *sz32 = (u32 *)(buff + SIPC5_LEN_OFFSET); + struct sk_buff_head *rxq = &iod->sk_rx_q; - memset(frm, 0, sizeof(struct sipc5_frame_data)); + skb_queue_tail(rxq, skb); - if (iod->format == IPC_CMD || - iod->format == IPC_BOOT || - iod->format == IPC_RAMDUMP) { - frm->len = count; + if (iod->format < IPC_MULTI_RAW && rxq->qlen > MAX_IOD_RXQ_LEN) { + struct sk_buff *victim = skb_dequeue(rxq); + mif_err("%s: %s application may be dead (rxq->qlen %d > %d)\n", + iod->name, iod->app ? iod->app : "corresponding", + rxq->qlen, MAX_IOD_RXQ_LEN); + if (victim) + dev_kfree_skb_any(victim); + return -ENOSPC; + } else { + mif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen); return 0; } +} - frm->config = SIPC5_START_MASK; +static int rx_drain(struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} - if (iod->format == IPC_FMT && count > 2048) { - frm->ctl_fld = true; - frm->config |= SIPC5_EXT_FIELD_EXIST; - frm->config |= SIPC5_CTL_FIELD_EXIST; - } +static int rx_loopback(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = skbpriv(skb)->ld; + int ret; - if (iod->id >= SIPC5_CH_ID_RFS_0 && count > 0xFFFF) { - frm->ext_len = true; - frm->config |= SIPC5_EXT_FIELD_EXIST; + ret = ld->send(ld, iod, skb); + if (ret < 0) { + mif_err("%s->%s: ERR! ld->send fail (err %d)\n", + iod->name, ld->name, ret); } - if (ld->aligned) - frm->config |= SIPC5_PADDING_EXIST; - - frm->ch_id = iod->id; - - frm->hdr_len = sipc5_get_hdr_len(frm->config); - frm->data_len = count; - frm->len = frm->hdr_len + frm->data_len; - - buff[SIPC5_CONFIG_OFFSET] = frm->config; - buff[SIPC5_CH_ID_OFFSET] = frm->ch_id; - - if (unlikely(frm->ext_len)) - *sz32 = (u32)frm->len; - else - *sz16 = (u16)frm->len; - - if (unlikely(frm->ctl_fld)) - buff[SIPC5_CTL_OFFSET] = frm->control; - - return frm->hdr_len; + return ret; } static int rx_fmt_frame(struct sk_buff *skb) { - struct io_device *iod = skbpriv(skb)->iod; struct link_device *ld = skbpriv(skb)->ld; - struct sk_buff_head *rxq = &iod->sk_rx_q; - struct sipc_fmt_hdr *fh; + struct io_device *iod = skbpriv(skb)->iod; struct sk_buff *rx_skb; - u8 ctrl = skbpriv(skb)->control; - unsigned id = ctrl & 0x7F; + int hdr_len = sipc5_get_hdr_len(skb->data); + u8 ctrl; + u8 id; - if (iod->skb[id] == NULL) { - /* - ** There has been no multiple frame with this ID. - */ - if ((ctrl & 0x80) == 0) { - /* - ** It is a single frame because the "more" bit is 0. - */ - skb_queue_tail(rxq, skb); - if (unlikely(rxq->qlen > 2048)) { - struct sk_buff *victim; - mif_info("%s: WARNING! rxq->qlen %d > 2048\n", - iod->name, rxq->qlen); - victim = skb_dequeue(rxq); - dev_kfree_skb_any(victim); - } else { - mif_debug("%s: rxq->qlen = %d\n", - iod->name, rxq->qlen); - } + if (!sipc5_multi_frame(skb->data)) { + skb_pull(skb, hdr_len); + queue_skb_to_iod(skb, iod); + wake_up(&iod->wq); + return 0; + } - wake_up(&iod->wq); - return 0; - } + /* Get the control field */ + ctrl = sipc5_get_ctrl_field(skb->data); - /* - ** The start of multiple frames - */ - fh = (struct sipc_fmt_hdr *)skb->data; - mif_debug("%s: start multi-frame (ID:%d len:%d)\n", - iod->name, id, fh->len); + /* Extract the control ID from the control field */ + id = ctrl & 0x7F; + + /* Remove SIPC5 link header */ + skb_pull(skb, hdr_len); + + /* If there has been no multiple frame with this ID, ... */ + if (iod->skb[id] == NULL) { + struct sipc_fmt_hdr *fh = (struct sipc_fmt_hdr *)skb->data; + + mif_err("%s->%s: start of multi-frame (ID:%d len:%d)\n", + ld->name, iod->name, id, fh->len); rx_skb = rx_alloc_skb(fh->len, iod, ld); if (!rx_skb) { - mif_info("%s: ERR! rx_alloc_skb fail\n", iod->name); + mif_err("%s: ERR! rx_alloc_skb fail\n", iod->name); return -ENOMEM; } @@ -313,31 +236,19 @@ static int rx_fmt_frame(struct sk_buff *skb) rx_skb = iod->skb[id]; } - /* - ** Start multi-frame processing - */ + /* Perform multi-frame processing */ memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); dev_kfree_skb_any(skb); if (ctrl & 0x80) { /* The last frame has not arrived yet. */ - mif_debug("%s: recv multi-frame (ID:%d rcvd:%d)\n", - iod->name, id, rx_skb->len); + mif_info("%s->%s: recv multi-frame (ID:%d rcvd:%d)\n", + ld->name, iod->name, id, rx_skb->len); } else { /* It is the last frame because the "more" bit is 0. */ - mif_debug("%s: end multi-frame (ID:%d rcvd:%d)\n", - iod->name, id, rx_skb->len); - skb_queue_tail(rxq, rx_skb); - if (unlikely(rxq->qlen > 2048)) { - struct sk_buff *victim; - mif_info("%s: WARNING! rxq->qlen %d > 2048\n", - iod->name, rxq->qlen); - victim = skb_dequeue(rxq); - dev_kfree_skb_any(victim); - } else { - mif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen); - } - + mif_err("%s->%s: end of multi-frame (ID:%d rcvd:%d)\n", + ld->name, iod->name, id, rx_skb->len); + queue_skb_to_iod(rx_skb, iod); iod->skb[id] = NULL; wake_up(&iod->wq); } @@ -345,76 +256,14 @@ static int rx_fmt_frame(struct sk_buff *skb) return 0; } -static int rx_rfs_frame(struct sk_buff *skb) -{ - struct io_device *iod = skbpriv(skb)->iod; - struct sk_buff_head *rxq = &iod->sk_rx_q; - - skb_queue_tail(rxq, skb); - if (unlikely(rxq->qlen > 2048)) { - struct sk_buff *victim; - mif_debug("%s: rxq->qlen %d > 2048\n", iod->name, rxq->qlen); - victim = skb_dequeue(rxq); - dev_kfree_skb_any(victim); - } else { - mif_debug("%s: rxq->qlen %d\n", iod->name, rxq->qlen); - } - - wake_up(&iod->wq); - - return 0; -} - -static int rx_loopback(struct sk_buff *skb) -{ - struct io_device *iod = skbpriv(skb)->iod; - struct link_device *ld = get_current_link(iod); - struct sipc5_frame_data frm; - unsigned headroom; - unsigned tailroom = 0; - int ret; - - headroom = tx_build_link_header(&frm, iod, ld, skb->len); - - if (ld->aligned) - tailroom = sipc5_calc_padding_size(headroom + skb->len); - - /* We need not to expand skb in here. dev_alloc_skb (in rx_alloc_skb) - * already alloc 32bytes padding in headroom. 32bytes are enough. - */ - - /* store IPC link header to start of skb - * this is skb_push not skb_put. different with misc_write. - */ - memcpy(skb_push(skb, headroom), frm.hdr, headroom); - - /* store padding */ - if (tailroom) - skb_put(skb, tailroom); - - /* forward */ - ret = ld->send(ld, iod, skb); - if (ret < 0) - mif_err("%s->%s: ld->send fail: %d\n", iod->name, - ld->name, ret); - return ret; -} - static int rx_raw_misc(struct sk_buff *skb) { - struct io_device *iod = skbpriv(skb)->iod; /* same with real_iod */ - struct sk_buff_head *rxq = &iod->sk_rx_q; + struct io_device *iod = skbpriv(skb)->iod; - skb_queue_tail(rxq, skb); - if (unlikely(rxq->qlen > 2048)) { - struct sk_buff *victim; - mif_debug("%s: rxq->qlen %d > 2048\n", iod->name, rxq->qlen); - victim = skb_dequeue(rxq); - dev_kfree_skb_any(victim); - } else { - mif_debug("%s: rxq->qlen %d\n", iod->name, rxq->qlen); - } + /* Remove the SIPC5 link header */ + skb_pull(skb, sipc5_get_hdr_len(skb->data)); + queue_skb_to_iod(skb, iod); wake_up(&iod->wq); return 0; @@ -422,12 +271,11 @@ static int rx_raw_misc(struct sk_buff *skb) static int rx_multi_pdp(struct sk_buff *skb) { - struct io_device *iod = skbpriv(skb)->iod; /* same with real_iod */ + struct link_device *ld = skbpriv(skb)->ld; + struct io_device *iod = skbpriv(skb)->iod; struct net_device *ndev; struct iphdr *iphdr; - struct ethhdr *ehdr; int ret; - const char source[ETH_ALEN] = SOURCE_MAC_ADDR; ndev = iod->ndev; if (!ndev) { @@ -435,6 +283,9 @@ static int rx_multi_pdp(struct sk_buff *skb) return -ENODEV; } + /* Remove the SIPC5 link header */ + skb_pull(skb, sipc5_get_hdr_len(skb->data)); + skb->dev = ndev; ndev->stats.rx_packets++; ndev->stats.rx_bytes += skb->len; @@ -447,14 +298,15 @@ static int rx_multi_pdp(struct sk_buff *skb) skb->protocol = htons(ETH_P_IP); if (iod->use_handover) { - skb_push(skb, sizeof(struct ethhdr)); - ehdr = (void *)skb->data; + struct ethhdr *ehdr; + const char source[ETH_ALEN] = SOURCE_MAC_ADDR; + + ehdr = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr)); memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN); memcpy(ehdr->h_source, source, ETH_ALEN); ehdr->h_proto = skb->protocol; skb->ip_summed = CHECKSUM_UNNECESSARY; skb_reset_mac_header(skb); - skb_pull(skb, sizeof(struct ethhdr)); } @@ -463,48 +315,82 @@ static int rx_multi_pdp(struct sk_buff *skb) else ret = netif_rx_ni(skb); - if (ret != NET_RX_SUCCESS) - mif_info("%s: ERR! netif_rx fail (err %d)\n", iod->name, ret); + if (ret != NET_RX_SUCCESS) { + mif_err("%s->%s: ERR! netif_rx fail (err %d)\n", + ld->name, iod->name, ret); + } return ret; } static int rx_demux(struct link_device *ld, struct sk_buff *skb) { - struct io_device *iod = NULL; - char *link = ld->name; - u8 ch = skbpriv(skb)->ch_id; + struct io_device *iod; + u8 ch = sipc5_get_ch_id(skb->data); +#ifdef DEBUG_MODEM_IF + struct modem_ctl *mc = ld->mc; + size_t len = (skb->len > 20) ? 20 : skb->len; + char tag[MIF_MAX_STR_LEN]; +#endif - if (unlikely(ch == SIPC5_CH_ID_MAX || ch == 0)) { - mif_info("%s: ERR! invalid ch# %d\n", link, ch); + if (unlikely(ch == 0)) { + mif_err("%s: ERR! invalid ch# %d\n", ld->name, ch); return -ENODEV; } + if (unlikely(ch == SIPC5_CH_ID_FLOW_CTRL)) + return netif_flow_ctrl(ld, skb); + /* IP loopback */ if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr) ch = RMNET0_CH_ID; iod = link_get_iod_with_channel(ld, ch); if (unlikely(!iod)) { - mif_info("%s: ERR! no iod for ch# %d\n", link, ch); + mif_err("%s: ERR! no iod with ch# %d\n", ld->name, ch); return -ENODEV; } skbpriv(skb)->ld = ld; skbpriv(skb)->iod = iod; - skbpriv(skb)->real_iod = iod; - /* don't care about CP2AP_LOOPBACK_CHANNEL is opened */ - if (unlikely(iod->id == CP2AP_LOOPBACK_CHANNEL)) + /* Don't care whether or not DATA_DRAIN_CHANNEL is opened */ + if (iod->id == DATA_DRAIN_CHANNEL) + return rx_drain(skb); + + /* Don't care whether or not DATA_LOOPBACK_CHANNEL is opened */ + if (iod->id == DATA_LOOPBACK_CHANNEL) return rx_loopback(skb); +#ifdef DEBUG_MODEM_IF + snprintf(tag, MIF_MAX_STR_LEN, "LNK: %s->%s", mc->name, iod->name); + if (unlikely(iod->format == IPC_FMT)) + pr_ipc(1, tag, skb->data, len); +#if 0 + if (iod->format == IPC_RAW) + pr_ipc(0, tag, skb->data, len); +#endif +#if 0 + if (iod->format == IPC_BOOT) + pr_ipc(0, tag, skb->data, len); +#endif +#if 0 + if (iod->format == IPC_RAMDUMP) + pr_ipc(0, tag, skb->data, len); +#endif +#if 0 + if (ch == 28) + pr_ipc(0, tag, skb->data, len); +#endif +#endif /*DEBUG_MODEM_IF*/ + if (atomic_read(&iod->opened) <= 0) { - mif_info("%s: ERR! %s is not opened\n", link, iod->name); + mif_err("%s: ERR! %s is not opened\n", ld->name, iod->name); return -ENODEV; } if (ch >= SIPC5_CH_ID_RFS_0) - return rx_rfs_frame(skb); + return rx_raw_misc(skb); else if (ch >= SIPC5_CH_ID_FMT_0) return rx_fmt_frame(skb); else if (iod->io_typ == IODEV_MISC) @@ -513,342 +399,337 @@ static int rx_demux(struct link_device *ld, struct sk_buff *skb) return rx_multi_pdp(skb); } -/* Check and store link layer header, then alloc an skb */ -static int rx_header_from_serial(struct io_device *iod, struct link_device *ld, - u8 *buff, unsigned size, struct sipc5_frame_data *frm) +/** + * rx_frame_config + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @buff: pointer to a buffer in which incoming data is stored + * @size: size of data in the buffer + * @frm: pointer to an instance of sipc5_frame_data structure + * + * 1) Checks a config field + * 2) Calculates the length of link layer header in an incoming frame and stores + * the value to "frm->hdr_len" + * 3) Stores the config field to "frm->hdr" and add the size of config field to + * "frm->hdr_rcvd" + * + * Returns the length of a config field that was copied to "frm" + */ +static int rx_frame_config(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct sipc5_frame_data *frm) { - char *link = ld->name; - struct sk_buff *skb; - int len; - u8 cfg = buff[0]; - - mif_debug("%s: size %d\n", link, size); - - if (!frm->config) { - if (unlikely(!sipc5_start_valid(cfg))) { - mif_info("%s: ERR! wrong start (0x%02x)\n", link, cfg); - return -EBADMSG; - } - rx_check_frame_cfg(cfg, frm); - - /* Copy the link layer header to the header buffer */ - len = min(frm->hdr_len, size); - memcpy(frm->hdr, buff, len); - } else { - /* Copy the link layer header to the header buffer */ - len = min((frm->hdr_len - frm->hdr_rcvd), size); - memcpy((frm->hdr + frm->hdr_rcvd), buff, len); - } + int rest; + int rcvd; - frm->hdr_rcvd += len; - - mif_debug("%s: FRM hdr_len:%d, hdr_rcvd:%d\n", - link, frm->hdr_len, frm->hdr_rcvd); - - if (frm->hdr_rcvd >= frm->hdr_len) { - rx_build_meta_data(ld, frm); - skb = rx_alloc_skb(frm->data_len, iod, ld); - fragdata(iod, ld)->skb_recv = skb; - skbpriv(skb)->ch_id = frm->ch_id; - skbpriv(skb)->control = frm->control; + if (unlikely(!sipc5_start_valid(buff))) { + mif_err("%s->%s: ERR! INVALID config 0x%02x\n", + ld->name, iod->name, buff[0]); + return -EBADMSG; } - return len; -} - -/* copy data to skb */ -static int rx_payload_from_serial(struct io_device *iod, struct link_device *ld, - u8 *buff, unsigned size, struct sipc5_frame_data *frm) -{ - struct sk_buff *skb = fragdata(iod, ld)->skb_recv; - char *link = ld->name; - unsigned rest = frm->data_len - frm->data_rcvd; - unsigned len; - - /* rest == (frm->data_len - frm->data_rcvd) == tailroom of skb */ - rest = frm->data_len - frm->data_rcvd; - mif_debug("%s: FRM data.len:%d data.rcvd:%d rest:%d size:%d\n", - link, frm->data_len, frm->data_rcvd, rest, size); - - /* If there is no skb, data must be dropped. */ - len = min(rest, size); - if (skb) - memcpy(skb_put(skb, len), buff, len); + frm->hdr_len = sipc5_get_hdr_len(buff); - frm->data_rcvd += len; + /* Calculate the size of a segment that will be copied */ + rest = frm->hdr_len; + rcvd = SIPC5_CONFIG_SIZE; + mif_debug("%s->%s: hdr_len:%d hdr_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->hdr_len, frm->hdr_rcvd, rest, size, + rcvd); - mif_debug("%s: FRM data_len:%d, data_rcvd:%d\n", - link, frm->data_len, frm->data_rcvd); + /* Copy the config field of an SIPC5 link header to the header buffer */ + memcpy(frm->hdr, buff, rcvd); + frm->hdr_rcvd += rcvd; - return len; + return rcvd; } -static int rx_frame_from_serial(struct io_device *iod, struct link_device *ld, - const char *data, unsigned size) +/** + * rx_frame_prepare_skb + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @frm: pointer to an instance of sipc5_frame_data structure + * + * 1) Extracts the length of a link frame from the link header in "frm->hdr" + * 2) Allocates an skb + * 3) Calculates the payload size in the link frame + * 4) Calculates the padding size in the link frame + * + * Returns the pointer to an skb + */ +static struct sk_buff *rx_frame_prepare_skb(struct io_device *iod, + struct link_device *ld, struct sipc5_frame_data *frm) { - struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; struct sk_buff *skb; - char *link = ld->name; - u8 *buff = (u8 *)data; - int rest = (int)size; - int err = 0; - int done = 0; - mif_debug("%s: size = %d\n", link, size); + /* Get the frame length */ + frm->len = sipc5_get_frame_len(frm->hdr); - if (frm->hdr_rcvd >= frm->hdr_len && frm->data_rcvd < frm->data_len) { - /* - ** There is an skb that is waiting for more SIPC5 data. - ** In this case, rx_header_from_serial() must be skipped. - */ - mif_debug("%s: FRM data.len:%d data.rcvd:%d -> recv_data\n", - link, frm->data_len, frm->data_rcvd); - goto recv_data; + /* Allocate an skb */ + skb = rx_alloc_skb(frm->len, iod, ld); + if (!skb) { + mif_err("%s->%s: ERR! rx_alloc_skb fail (size %d)\n", + ld->name, iod->name, frm->len); + return NULL; } -next_frame: - /* Receive and analyze header, then prepare an akb */ - err = done = rx_header_from_serial(iod, ld, buff, rest, frm); - if (err < 0) - goto err_exit; - - buff += done; - rest -= done; - mif_debug("%s: rx_header() -> done:%d rest:%d\n", link, done, rest); - if (rest < 0) - goto err_range; + /* Calculates the payload size */ + frm->pay_len = frm->len - frm->hdr_len; - if (rest == 0) - return size; + /* Calculates the padding size */ + if (sipc5_padding_exist(frm->hdr)) + frm->pad_len = sipc5_calc_padding_size(frm->len); -recv_data: - err = 0; + mif_debug("%s->%s: size %d (header:%d payload:%d padding:%d)\n", + ld->name, iod->name, frm->len, frm->hdr_len, frm->pay_len, + frm->pad_len); - mif_debug("%s: done:%d rest:%d -> rx_payload()\n", link, done, rest); - - done = rx_payload_from_serial(iod, ld, buff, rest, frm); - buff += done; - rest -= done; + return skb; +} - mif_debug("%s: rx_payload() -> done:%d rest:%d\n", link, done, rest); +/** + * rx_frame_header + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @buff: pointer to a buffer in which incoming data is stored + * @size: size of data in the buffer + * @frm: pointer to an instance of sipc5_frame_data structure + * + * 1) Stores a link layer header to "frm->hdr" temporarily while "frm->hdr_rcvd" + * is less than "frm->hdr_len" + * 2) Then, + * Allocates an skb + * Copies the link header from "frm" to "skb" + * Register the skb to receive payload + * + * Returns the size of a segment that was copied to "frm" + */ +static int rx_frame_header(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct sipc5_frame_data *frm) +{ + struct sk_buff *skb; + int rest; + int rcvd; - if (rest == 0 && frm->data_rcvd < frm->data_len) { - /* - Data is being received and more data will come within the next - frame from the link device. - */ - return size; - } + /* Calculate the size of a segment that will be copied */ + rest = frm->hdr_len - frm->hdr_rcvd; + rcvd = min(rest, size); + mif_debug("%s->%s: hdr_len:%d hdr_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->hdr_len, frm->hdr_rcvd, rest, size, + rcvd); - /* At this point, one complete link layer frame has been received. */ + /* Copy a segment of an SIPC5 link header to "frm" */ + memcpy((frm->hdr + frm->hdr_rcvd), buff, rcvd); + frm->hdr_rcvd += rcvd; - /* A padding size is applied to access the next IPC frame. */ - if (frm->padding) { - done = sipc5_calc_padding_size(frm->len); - if (done > rest) { - mif_info("%s: ERR! padding %d > rest %d\n", - link, done, rest); - goto err_exit; + if (frm->hdr_rcvd >= frm->hdr_len) { + /* Prepare an skb with the information in {iod, ld, frm} */ + skb = rx_frame_prepare_skb(iod, ld, frm); + if (!skb) { + mif_err("%s->%s: ERR! rx_frame_prepare_skb fail\n", + ld->name, iod->name); + return -ENOMEM; } - buff += done; - rest -= done; - - mif_debug("%s: padding:%d -> rest:%d\n", link, done, rest); - - if (rest < 0) - goto err_range; - - } - - skb = fragdata(iod, ld)->skb_recv; - if (likely(skb)) { - mif_debug("%s: len:%d -> rx_demux()\n", link, skb->len); - err = rx_demux(ld, skb); - if (err < 0) - dev_kfree_skb_any(skb); - } else { - mif_debug("%s: len:%d -> drop\n", link, skb->len); - } - - /* initialize the skb_recv and the frame_data buffer */ - fragdata(iod, ld)->skb_recv = NULL; - memset(frm, 0, sizeof(struct sipc5_frame_data)); + /* Copy an SIPC5 link header from "frm" to "skb" */ + memcpy(skb_put(skb, frm->hdr_len), frm->hdr, frm->hdr_len); - if (rest > 0) - goto next_frame; - - if (rest <= 0) - return size; - -err_exit: - if (fragdata(iod, ld)->skb_recv && - frm->hdr_rcvd >= frm->hdr_len && frm->data_rcvd >= frm->data_len) { - dev_kfree_skb_any(fragdata(iod, ld)->skb_recv); - memset(frm, 0, sizeof(struct sipc5_frame_data)); - fragdata(iod, ld)->skb_recv = NULL; - mif_info("%s: ERR! clear frag\n", link); + /* Register the skb to receive payload */ + fragdata(iod, ld)->skb_recv = skb; } - return err; -err_range: - mif_info("%s: ERR! size:%d vs. rest:%d\n", link, size, rest); - return size; + return rcvd; } /** - * rx_header_from_mem - * @ld: pointer to the link device - * @buff: pointer to the frame - * @rest: size of the frame - * @frm: pointer to the sipc5_frame_data buffer + * rx_frame_payload + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @buff: pointer to a buffer in which incoming data is stored + * @size: size of data in the buffer + * @frm: pointer to an instance of sipc5_frame_data structure * - * 1) Verifies a link layer header configuration of a frame - * 2) Stores the link layer header to the header buffer - * 3) Builds and stores the meta data of the frame into a meta data buffer - * 4) Verifies the length of the frame + * Stores a link layer payload to "skb" * - * Returns SIPC5 header length + * Returns the size of a segment that was copied to "skb" */ -static int rx_header_from_mem(struct link_device *ld, u8 *buff, unsigned rest, - struct sipc5_frame_data *frm) +static int rx_frame_payload(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct sipc5_frame_data *frm) { - char *link = ld->name; - u8 cfg = buff[0]; + struct sk_buff *skb = fragdata(iod, ld)->skb_recv; + int rest; + int rcvd; - /* Verify link layer header configuration */ - if (unlikely(!sipc5_start_valid(cfg))) { - mif_info("%s: ERR! wrong start (0x%02x)\n", link, cfg); - return -EBADMSG; - } - rx_check_frame_cfg(cfg, frm); + /* Calculate the size of a segment that will be copied */ + rest = frm->pay_len - frm->pay_rcvd; + rcvd = min(rest, size); + mif_debug("%s->%s: pay_len:%d pay_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->pay_len, frm->pay_rcvd, rest, size, + rcvd); - /* Store the link layer header to the header buffer */ - memcpy(frm->hdr, buff, frm->hdr_len); - frm->hdr_rcvd = frm->hdr_len; + /* Copy an SIPC5 link payload to "skb" */ + memcpy(skb_put(skb, rcvd), buff, rcvd); + frm->pay_rcvd += rcvd; - /* Build and store the meta data of this frame */ - rx_build_meta_data(ld, frm); + return rcvd; +} - /* Verify frame length */ - if (unlikely(frm->len > rest)) { - mif_info("%s: ERR! frame length %d > rest %d\n", - link, frm->len, rest); - return -EBADMSG; - } +static int rx_frame_padding(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct sipc5_frame_data *frm) +{ + struct sk_buff *skb = fragdata(iod, ld)->skb_recv; + int rest; + int rcvd; + + /* Calculate the size of a segment that will be dropped as padding */ + rest = frm->pad_len - frm->pad_rcvd; + rcvd = min(rest, size); + mif_debug("%s->%s: pad_len:%d pad_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->pad_len, frm->pad_rcvd, rest, size, + rcvd); - return frm->hdr_rcvd; + /* Copy an SIPC5 link padding to "skb" */ + memcpy(skb_put(skb, rcvd), buff, rcvd); + frm->pad_rcvd += rcvd; + + return rcvd; } -/* copy data to skb */ -static int rx_payload_from_mem(struct sk_buff *skb, u8 *buff, unsigned len) +static int rx_frame_done(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb) { - /* If there is no skb, data must be dropped. */ - if (skb) - memcpy(skb_put(skb, len), buff, len); - return len; + /* Cut off the padding of the current frame */ + skb_trim(skb, sipc5_get_frame_len(skb->data)); + mif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len); + + return rx_demux(ld, skb); } -static int rx_frame_from_mem(struct io_device *iod, struct link_device *ld, +static int recv_frame_from_buff(struct io_device *iod, struct link_device *ld, const char *data, unsigned size) { struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; struct sk_buff *skb; - char *link = ld->name; u8 *buff = (u8 *)data; int rest = (int)size; - int len; - int done; + int done = 0; + int err = 0; - mif_debug("%s: size = %d\n", link, size); + mif_debug("%s->%s: size %d (RX state = %s)\n", ld->name, iod->name, + size, get_rx_state_str(iod->curr_rx_state)); while (rest > 0) { - /* Initialize the frame data buffer */ - memset(frm, 0, sizeof(struct sipc5_frame_data)); - skb = NULL; + switch (iod->curr_rx_state) { + case IOD_RX_ON_STANDBY: + fragdata(iod, ld)->skb_recv = NULL; + memset(frm, 0, sizeof(struct sipc5_frame_data)); + + done = rx_frame_config(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } - /* Receive and analyze link layer header */ - done = rx_header_from_mem(ld, buff, rest, frm); - if (unlikely(done < 0)) - return -EBADMSG; + iod->next_rx_state = IOD_RX_HEADER; - /* Verify rest size */ - rest -= done; - if (rest < 0) { - mif_info("%s: ERR! rx_header -> rest %d\n", link, rest); - return -ERANGE; - } + break; - /* Move buff pointer to the payload */ - buff += done; + case IOD_RX_HEADER: + done = rx_frame_header(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } - /* Prepare an akb */ - len = frm->data_len; - skb = rx_alloc_skb(len, iod, ld); + if (frm->hdr_rcvd >= frm->hdr_len) + iod->next_rx_state = IOD_RX_PAYLOAD; + else + iod->next_rx_state = IOD_RX_HEADER; - /* Store channel ID and control fields to the CB of the skb */ - skbpriv(skb)->ch_id = frm->ch_id; - skbpriv(skb)->control = frm->control; + break; - /* Receive payload */ - mif_debug("%s: done:%d rest:%d len:%d -> rx_payload()\n", - link, done, rest, len); - done = rx_payload_from_mem(skb, buff, len); - rest -= done; - if (rest < 0) { - mif_info("%s: ERR! rx_payload() -> rest %d\n", - link, rest); - if (skb) - dev_kfree_skb_any(skb); - return -ERANGE; - } - buff += done; + case IOD_RX_PAYLOAD: + done = rx_frame_payload(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } - /* A padding size is applied to access the next IPC frame. */ - if (frm->padding) { - done = sipc5_calc_padding_size(frm->len); - if (done > rest) { - mif_info("%s: ERR! padding %d > rest %d\n", - link, done, rest); - if (skb) - dev_kfree_skb_any(skb); - return -ERANGE; + if (frm->pay_rcvd >= frm->pay_len) { + if (frm->pad_len > 0) + iod->next_rx_state = IOD_RX_PADDING; + else + iod->next_rx_state = IOD_RX_ON_STANDBY; + } else { + iod->next_rx_state = IOD_RX_PAYLOAD; } - buff += done; - rest -= done; + + break; + + case IOD_RX_PADDING: + done = rx_frame_padding(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } + + if (frm->pad_rcvd >= frm->pad_len) + iod->next_rx_state = IOD_RX_ON_STANDBY; + else + iod->next_rx_state = IOD_RX_PADDING; + + break; + + default: + mif_err("%s->%s: ERR! INVALID RX state %d\n", + ld->name, iod->name, iod->curr_rx_state); + err = -EINVAL; + goto err_exit; } - if (likely(skb)) { - mif_debug("%s: len:%d -> rx_demux()\n", link, skb->len); - if (rx_demux(ld, skb) < 0) - dev_kfree_skb_any(skb); - } else { - mif_debug("%s: len:%d -> drop\n", link, skb->len); + if (iod->next_rx_state == IOD_RX_ON_STANDBY) { + /* + ** A complete frame is in fragdata(iod, ld)->skb_recv. + */ + skb = fragdata(iod, ld)->skb_recv; + err = rx_frame_done(iod, ld, skb); + if (err < 0) + goto err_exit; } + + buff += done; + rest -= done; + if (rest < 0) + goto err_range; + + iod->curr_rx_state = iod->next_rx_state; } - return 0; + return size; + +err_exit: + if (fragdata(iod, ld)->skb_recv) { + mif_err("%s->%s: ERR! clear frag (size:%d done:%d rest:%d)\n", + ld->name, iod->name, size, done, rest); + dev_kfree_skb_any(fragdata(iod, ld)->skb_recv); + fragdata(iod, ld)->skb_recv = NULL; + } + iod->curr_rx_state = IOD_RX_ON_STANDBY; + return err; + +err_range: + mif_err("%s->%s: ERR! size:%d done:%d rest:%d\n", + ld->name, iod->name, size, done, rest); + iod->curr_rx_state = IOD_RX_ON_STANDBY; + return size; } /* called from link device when a packet arrives for this io device */ static int io_dev_recv_data_from_link_dev(struct io_device *iod, struct link_device *ld, const char *data, unsigned int len) { - struct sk_buff_head *rxq = &iod->sk_rx_q; struct sk_buff *skb; - char *link = ld->name; int err; - if (!data) { - mif_info("%s: ERR! !data\n", link); - return -EINVAL; - } - - if (len <= 0) { - mif_info("%s: ERR! len %d <= 0\n", link, len); - return -EINVAL; - } - switch (iod->format) { case IPC_FMT: case IPC_RAW: @@ -857,97 +738,151 @@ static int io_dev_recv_data_from_link_dev(struct io_device *iod, if (iod->waketime) wake_lock_timeout(&iod->wakelock, iod->waketime); - if (ld->link_type == LINKDEV_DPRAM && ld->aligned) - err = rx_frame_from_mem(iod, ld, data, len); - else - err = rx_frame_from_serial(iod, ld, data, len); - - if (err < 0) - mif_info("%s: ERR! rx_frame_from_link fail (err %d)\n", - link, err); + err = recv_frame_from_buff(iod, ld, data, len); + if (err < 0) { + mif_err("%s->%s: ERR! recv_frame_from_buff fail " + "(err %d)\n", ld->name, iod->name, err); + } return err; - case IPC_CMD: - case IPC_BOOT: - case IPC_RAMDUMP: + default: + mif_debug("%s->%s: len %d\n", ld->name, iod->name, len); + /* save packet to sk_buff */ skb = rx_alloc_skb(len, iod, ld); if (!skb) { - mif_info("%s: ERR! rx_alloc_skb fail\n", link); + mif_info("%s->%s: ERR! rx_alloc_skb fail\n", + ld->name, iod->name); return -ENOMEM; } - mif_debug("%s: len:%d -> iod:%s\n", link, len, iod->name); - memcpy(skb_put(skb, len), data, len); - skb_queue_tail(rxq, skb); - if (unlikely(rxq->qlen > 2048)) { - struct sk_buff *victim; - mif_info("%s: ERR! rxq->qlen %d > 2048\n", - iod->name, rxq->qlen); - victim = skb_dequeue(rxq); - dev_kfree_skb_any(victim); - } + + queue_skb_to_iod(skb, iod); + wake_up(&iod->wq); return len; - - default: - mif_info("%s: ERR! unknown format %d\n", link, iod->format); - return -EINVAL; } } -static int rx_frame_from_skb(struct io_device *iod, struct link_device *ld, +static int recv_frame_from_skb(struct io_device *iod, struct link_device *ld, struct sk_buff *skb) { - struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; - u8 cfg = skb->data[0]; + struct sk_buff *clone; + unsigned int rest; + unsigned int rcvd; + int tot; /* total length including padding */ + int err = 0; - /* Initialize the frame data buffer */ - memset(frm, 0, sizeof(struct sipc5_frame_data)); + /* + ** If there is only one SIPC5 frame in @skb, receive the SIPC5 frame and + ** return immediately. In this case, the frame verification must already + ** have been done at the link device. + */ + if (skbpriv(skb)->single_frame) { + err = rx_frame_done(iod, ld, skb); + if (err < 0) + goto exit; + return 0; + } /* - ** The start of a link layer header has already been checked in the - ** link device. + ** The routine from here is used only if there may be multiple SIPC5 + ** frames in @skb. */ - /* Analyze the configuration of the link layer header */ - rx_check_frame_cfg(cfg, frm); + /* Check the config field of the first frame in @skb */ + if (!sipc5_start_valid(skb->data)) { + mif_err("%s->%s: ERR! INVALID config 0x%02X\n", + ld->name, iod->name, skb->data[0]); + err = -EINVAL; + goto exit; + } + + /* Get the total length of the frame with a padding */ + tot = sipc5_get_total_len(skb->data); - /* Store the link layer header to the header buffer */ - memcpy(frm->hdr, skb->data, frm->hdr_len); - frm->hdr_rcvd = frm->hdr_len; + /* Verify the total length of the first frame */ + rest = skb->len; + if (unlikely(tot > rest)) { + mif_err("%s->%s: ERR! tot %d > skb->len %d)\n", + ld->name, iod->name, tot, rest); + err = -EINVAL; + goto exit; + } - /* Build and store the meta data of this frame */ - rx_build_meta_data(ld, frm); + /* If there is only one SIPC5 frame in @skb, */ + if (likely(tot == rest)) { + /* Receive the SIPC5 frame and return immediately */ + err = rx_frame_done(iod, ld, skb); + if (err < 0) + goto exit; + return 0; + } /* - ** The length of the frame has already been checked in the link device. + ** This routine is used only if there are multiple SIPC5 frames in @skb. */ + rcvd = 0; + while (rest > 0) { + clone = skb_clone(skb, GFP_ATOMIC); + if (unlikely(!clone)) { + mif_err("%s->%s: ERR! skb_clone fail\n", + ld->name, iod->name); + err = -ENOMEM; + goto exit; + } - /* Trim the link layer header off the frame */ - skb_pull(skb, frm->hdr_len); + /* Get the start of an SIPC5 frame */ + skb_pull(clone, rcvd); + if (!sipc5_start_valid(clone->data)) { + mif_err("%s->%s: ERR! INVALID config 0x%02X\n", + ld->name, iod->name, clone->data[0]); + dev_kfree_skb_any(clone); + err = -EINVAL; + goto exit; + } - /* Store channel ID and control fields to the CB of the skb */ - skbpriv(skb)->ch_id = frm->ch_id; - skbpriv(skb)->control = frm->control; + /* Get the total length of the current frame with a padding */ + tot = sipc5_get_total_len(clone->data); + if (unlikely(tot > rest)) { + mif_err("%s->%s: ERR! dirty frame (tot %d > rest %d)\n", + ld->name, iod->name, tot, rest); + dev_kfree_skb_any(clone); + err = -EINVAL; + goto exit; + } - /* Demux the frame */ - if (rx_demux(ld, skb) < 0) { - mif_err("%s: ERR! rx_demux fail\n", ld->name); - return -EINVAL; + /* Cut off the padding of the current frame */ + skb_trim(clone, sipc5_get_frame_len(clone->data)); + + /* Demux the frame */ + err = rx_demux(ld, clone); + if (err < 0) { + mif_err("%s->%s: ERR! rx_demux fail (err %d)\n", + ld->name, iod->name, err); + dev_kfree_skb_any(clone); + goto exit; + } + + /* Calculate the start of the next frame */ + rcvd += tot; + + /* Calculate the rest size of data in @skb */ + rest -= tot; } - return 0; +exit: + dev_kfree_skb_any(skb); + return err; } /* called from link device when a packet arrives for this io device */ static int io_dev_recv_skb_from_link_dev(struct io_device *iod, struct link_device *ld, struct sk_buff *skb) { - char *link = ld->name; enum dev_format dev = iod->format; int err; @@ -959,17 +894,35 @@ static int io_dev_recv_skb_from_link_dev(struct io_device *iod, if (iod->waketime) wake_lock_timeout(&iod->wakelock, iod->waketime); - err = rx_frame_from_skb(iod, ld, skb); + err = recv_frame_from_skb(iod, ld, skb); if (err < 0) { - dev_kfree_skb_any(skb); - mif_info("%s: ERR! rx_frame_from_skb fail (err %d)\n", - link, err); + mif_err("%s->%s: ERR! recv_frame_from_skb fail " + "(err %d)\n", ld->name, iod->name, err); + } + + return err; + + case IPC_BOOT: + case IPC_RAMDUMP: + if (!iod->id) { + mif_err("%s->%s: ERR! invalid iod\n", + ld->name, iod->name); + return -ENODEV; + } + + if (iod->waketime) + wake_lock_timeout(&iod->wakelock, iod->waketime); + + err = recv_frame_from_skb(iod, ld, skb); + if (err < 0) { + mif_err("%s->%s: ERR! recv_frame_from_skb fail " + "(err %d)\n", ld->name, iod->name, err); } return err; default: - mif_info("%s: ERR! unknown device %d\n", link, dev); + mif_err("%s->%s: ERR! invalid iod\n", ld->name, iod->name); return -EINVAL; } } @@ -980,10 +933,14 @@ static int io_dev_recv_skb_from_link_dev(struct io_device *iod, static void io_dev_modem_state_changed(struct io_device *iod, enum modem_state state) { - mif_info("%s: %s state changed (state %d)\n", - iod->name, iod->mc->name, state); + struct modem_ctl *mc = iod->mc; + int old_state = mc->phone_state; - iod->mc->phone_state = state; + if (old_state != state) { + mc->phone_state = state; + mif_err("%s state changed (%s -> %s)\n", mc->name, + get_cp_state_str(old_state), get_cp_state_str(state)); + } if (state == STATE_CRASH_RESET || state == STATE_CRASH_EXIT || state == STATE_NV_REBUILDING) @@ -1024,23 +981,27 @@ static int misc_open(struct inode *inode, struct file *filp) struct io_device *iod = to_io_device(filp->private_data); struct modem_shared *msd = iod->msd; struct link_device *ld; + int ref_cnt; int ret; filp->private_data = (void *)iod; - atomic_inc(&iod->opened); - list_for_each_entry(ld, &msd->link_dev_list, list) { if (IS_CONNECTED(iod, ld) && ld->init_comm) { ret = ld->init_comm(ld, iod); if (ret < 0) { - mif_info("%s: init_comm fail(%d)\n", - ld->name, ret); + mif_err("%s<->%s: ERR! init_comm fail(%d)\n", + iod->name, ld->name, ret); return ret; } } } - mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened)); + ref_cnt = atomic_inc_return(&iod->opened); + + if (iod->format == IPC_BOOT || iod->format == IPC_RAMDUMP) + mif_err("%s (opened %d)\n", iod->name, ref_cnt); + else + mif_info("%s (opened %d)\n", iod->name, ref_cnt); return 0; } @@ -1050,8 +1011,8 @@ static int misc_release(struct inode *inode, struct file *filp) struct io_device *iod = (struct io_device *)filp->private_data; struct modem_shared *msd = iod->msd; struct link_device *ld; + int ref_cnt; - atomic_dec(&iod->opened); skb_queue_purge(&iod->sk_rx_q); list_for_each_entry(ld, &msd->link_dev_list, list) { @@ -1059,7 +1020,12 @@ static int misc_release(struct inode *inode, struct file *filp) ld->terminate_comm(ld, iod); } - mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened)); + ref_cnt = atomic_dec_return(&iod->opened); + + if (iod->format == IPC_BOOT || iod->format == IPC_RAMDUMP) + mif_err("%s (opened %d)\n", iod->name, ref_cnt); + else + mif_info("%s (opened %d)\n", iod->name, ref_cnt); return 0; } @@ -1067,20 +1033,23 @@ static int misc_release(struct inode *inode, struct file *filp) static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) { struct io_device *iod = (struct io_device *)filp->private_data; + struct modem_ctl *mc = iod->mc; poll_wait(filp, &iod->wq, wait); - if (!skb_queue_empty(&iod->sk_rx_q) && - iod->mc->phone_state != STATE_OFFLINE) { + if (!skb_queue_empty(&iod->sk_rx_q) && mc->phone_state != STATE_OFFLINE) return POLLIN | POLLRDNORM; - } else if ((iod->mc->phone_state == STATE_CRASH_RESET) || - (iod->mc->phone_state == STATE_CRASH_EXIT) || - (iod->mc->phone_state == STATE_NV_REBUILDING) || - (iod->mc->sim_state.changed)) { + + if (mc->phone_state == STATE_CRASH_RESET + || mc->phone_state == STATE_CRASH_EXIT + || mc->phone_state == STATE_NV_REBUILDING + || mc->sim_state.changed) { if (iod->format == IPC_RAW) { msleep(20); return 0; } + if (iod->format == IPC_RAMDUMP) + return 0; return POLLHUP; } else { return 0; @@ -1089,132 +1058,191 @@ static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { - int p_state; struct io_device *iod = (struct io_device *)filp->private_data; struct link_device *ld = get_current_link(iod); - char cpinfo_buf[530] = "CP Crash "; + struct modem_ctl *mc = iod->mc; + int p_state; + char *buff; + void __user *user_buff; unsigned long size; - int ret; switch (cmd) { case IOCTL_MODEM_ON: - mif_info("%s: IOCTL_MODEM_ON\n", iod->name); - return iod->mc->ops.modem_on(iod->mc); + if (mc->ops.modem_on) { + mif_err("%s: IOCTL_MODEM_ON\n", iod->name); + return mc->ops.modem_on(mc); + } + mif_err("%s: !mc->ops.modem_on\n", iod->name); + return -EINVAL; case IOCTL_MODEM_OFF: - mif_info("%s: IOCTL_MODEM_OFF\n", iod->name); - return iod->mc->ops.modem_off(iod->mc); + if (mc->ops.modem_off) { + mif_err("%s: IOCTL_MODEM_OFF\n", iod->name); + return mc->ops.modem_off(mc); + } + mif_err("%s: !mc->ops.modem_off\n", iod->name); + return -EINVAL; case IOCTL_MODEM_RESET: - mif_info("%s: IOCTL_MODEM_RESET\n", iod->name); - return iod->mc->ops.modem_reset(iod->mc); + if (mc->ops.modem_reset) { + mif_err("%s: IOCTL_MODEM_RESET\n", iod->name); + return mc->ops.modem_reset(mc); + } + mif_err("%s: !mc->ops.modem_reset\n", iod->name); + return -EINVAL; case IOCTL_MODEM_BOOT_ON: - mif_info("%s: IOCTL_MODEM_BOOT_ON\n", iod->name); - return iod->mc->ops.modem_boot_on(iod->mc); + if (mc->ops.modem_boot_on) { + mif_err("%s: IOCTL_MODEM_BOOT_ON\n", iod->name); + return mc->ops.modem_boot_on(mc); + } + mif_err("%s: !mc->ops.modem_boot_on\n", iod->name); + return -EINVAL; case IOCTL_MODEM_BOOT_OFF: - mif_info("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name); - return iod->mc->ops.modem_boot_off(iod->mc); + if (mc->ops.modem_boot_off) { + mif_err("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name); + return mc->ops.modem_boot_off(mc); + } + mif_err("%s: !mc->ops.modem_boot_off\n", iod->name); + return -EINVAL; case IOCTL_MODEM_BOOT_DONE: mif_err("%s: IOCTL_MODEM_BOOT_DONE\n", iod->name); - if (iod->mc->ops.modem_boot_done) - return iod->mc->ops.modem_boot_done(iod->mc); - else - return 0; + if (mc->ops.modem_boot_done) + return mc->ops.modem_boot_done(mc); + return 0; case IOCTL_MODEM_STATUS: mif_debug("%s: IOCTL_MODEM_STATUS\n", iod->name); - p_state = iod->mc->phone_state; + p_state = mc->phone_state; if ((p_state == STATE_CRASH_RESET) || (p_state == STATE_CRASH_EXIT)) { - mif_info("%s: IOCTL_MODEM_STATUS (state %d)\n", - iod->name, p_state); - } else if (iod->mc->sim_state.changed) { - int s_state = iod->mc->sim_state.online ? + mif_info("%s: IOCTL_MODEM_STATUS (state %s)\n", + iod->name, get_cp_state_str(p_state)); + } else if (mc->sim_state.changed) { + int s_state = mc->sim_state.online ? STATE_SIM_ATTACH : STATE_SIM_DETACH; - iod->mc->sim_state.changed = false; + mc->sim_state.changed = false; return s_state; } else if (p_state == STATE_NV_REBUILDING) { - mif_info("%s: IOCTL_MODEM_STATUS (state %d)\n", - iod->name, p_state); - iod->mc->phone_state = STATE_ONLINE; + mif_info("%s: IOCTL_MODEM_STATUS (state %s)\n", + iod->name, get_cp_state_str(p_state)); + mc->phone_state = STATE_ONLINE; } return p_state; - case IOCTL_MODEM_PROTOCOL_SUSPEND: - mif_debug("%s: IOCTL_MODEM_PROTOCOL_SUSPEND\n", - iod->name); - - if (iod->format != IPC_MULTI_RAW) - return -EINVAL; + case IOCTL_MODEM_XMIT_BOOT: + if (ld->xmit_boot) { + mif_info("%s: IOCTL_MODEM_XMIT_BOOT\n", iod->name); + return ld->xmit_boot(ld, iod, arg); + } + mif_err("%s: !ld->xmit_boot\n", iod->name); + return -EINVAL; - iodevs_for_each(iod->msd, iodev_netif_stop, 0); - return 0; + case IOCTL_MODEM_DL_START: + if (ld->dload_start) { + mif_info("%s: IOCTL_MODEM_DL_START\n", iod->name); + return ld->dload_start(ld, iod); + } + mif_err("%s: !ld->dload_start\n", iod->name); + return -EINVAL; - case IOCTL_MODEM_PROTOCOL_RESUME: - mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n", - iod->name); + case IOCTL_MODEM_FW_UPDATE: + if (ld->firm_update) { + mif_info("%s: IOCTL_MODEM_FW_UPDATE\n", iod->name); + return ld->firm_update(ld, iod, arg); + } + mif_err("%s: !ld->firm_update\n", iod->name); + return -EINVAL; - if (iod->format != IPC_MULTI_RAW) - return -EINVAL; + case IOCTL_MODEM_FORCE_CRASH_EXIT: + if (mc->ops.modem_force_crash_exit) { + mif_err("%s: IOCTL_MODEM_FORCE_CRASH_EXIT\n", + iod->name); + return mc->ops.modem_force_crash_exit(mc); + } + mif_err("%s: !mc->ops.modem_force_crash_exit\n", iod->name); + return -EINVAL; - iodevs_for_each(iod->msd, iodev_netif_wake, 0); - return 0; + case IOCTL_MODEM_DUMP_RESET: + if (mc->ops.modem_dump_reset) { + mif_info("%s: IOCTL_MODEM_DUMP_RESET\n", iod->name); + return mc->ops.modem_dump_reset(mc); + } + mif_err("%s: !mc->ops.modem_dump_reset\n", iod->name); + return -EINVAL; case IOCTL_MODEM_DUMP_START: - mif_info("%s: IOCTL_MODEM_DUMP_START\n", iod->name); - return ld->dump_start(ld, iod); + if (ld->dump_start) { + mif_err("%s: IOCTL_MODEM_DUMP_START\n", iod->name); + return ld->dump_start(ld, iod); + } + mif_err("%s: !ld->dump_start\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_RAMDUMP_START: + if (ld->dump_start) { + mif_info("%s: IOCTL_MODEM_RAMDUMP_START\n", iod->name); + return ld->dump_start(ld, iod); + } + mif_err("%s: !ld->dump_start\n", iod->name); + return -EINVAL; case IOCTL_MODEM_DUMP_UPDATE: - mif_debug("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name); - return ld->dump_update(ld, iod, arg); + if (ld->dump_update) { + mif_info("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name); + return ld->dump_update(ld, iod, arg); + } + mif_err("%s: !ld->dump_update\n", iod->name); + return -EINVAL; - case IOCTL_MODEM_FORCE_CRASH_EXIT: - mif_info("%s: IOCTL_MODEM_FORCE_CRASH_EXIT\n", iod->name); - if (iod->mc->ops.modem_force_crash_exit) - return iod->mc->ops.modem_force_crash_exit(iod->mc); + case IOCTL_MODEM_RAMDUMP_STOP: + if (ld->dump_finish) { + mif_info("%s: IOCTL_MODEM_RAMDUMP_STOP\n", iod->name); + return ld->dump_finish(ld, iod, arg); + } + mif_err("%s: !ld->dump_finish\n", iod->name); return -EINVAL; case IOCTL_MODEM_CP_UPLOAD: mif_info("%s: IOCTL_MODEM_CP_UPLOAD\n", iod->name); - if (copy_from_user(cpinfo_buf + strlen(cpinfo_buf), - (void __user *)arg, MAX_CPINFO_SIZE) != 0) - return -EFAULT; - panic(cpinfo_buf); + strcpy(iod->msd->cp_crash_info, CP_CRASH_TAG); + if (arg) { + buff = iod->msd->cp_crash_info + strlen(CP_CRASH_TAG); + user_buff = (void __user *)arg; + if (copy_from_user(buff, user_buff, MAX_CPINFO_SIZE)) + return -EFAULT; + } + panic(iod->msd->cp_crash_info); return 0; - case IOCTL_MODEM_DUMP_RESET: - mif_info("%s: IOCTL_MODEM_DUMP_RESET\n", iod->name); - return iod->mc->ops.modem_dump_reset(iod->mc); + case IOCTL_MODEM_PROTOCOL_SUSPEND: + mif_info("%s: IOCTL_MODEM_PROTOCOL_SUSPEND\n", iod->name); + if (iod->format == IPC_MULTI_RAW) { + iodevs_for_each(iod->msd, iodev_netif_stop, 0); + return 0; + } + return -EINVAL; + + case IOCTL_MODEM_PROTOCOL_RESUME: + mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n", iod->name); + if (iod->format != IPC_MULTI_RAW) { + iodevs_for_each(iod->msd, iodev_netif_wake, 0); + return 0; + } + return -EINVAL; case IOCTL_MIF_LOG_DUMP: iodevs_for_each(iod->msd, iodev_dump_status, 0); + user_buff = (void __user *)arg; size = MAX_MIF_BUFF_SIZE; - ret = copy_to_user((void __user *)arg, &size, - sizeof(unsigned long)); - if (ret < 0) + if (copy_to_user(user_buff, &size, sizeof(unsigned long))) return -EFAULT; - - mif_dump_log(iod->mc->msd, iod); + mif_dump_log(mc->msd, iod); return 0; - case IOCTL_MIF_DPRAM_DUMP: -#ifdef CONFIG_LINK_DEVICE_DPRAM - if (iod->mc->mdm_data->link_types & LINKTYPE(LINKDEV_DPRAM)) { - size = iod->mc->mdm_data->dpram_ctl->dp_size; - ret = copy_to_user((void __user *)arg, &size, - sizeof(unsigned long)); - if (ret < 0) - return -EFAULT; - mif_dump_dpram(iod); - return 0; - } -#endif - return -EINVAL; - default: /* If you need to handle the ioctl for specific link device, * then assign the link ioctl handler to ld->ioctl @@ -1222,9 +1250,10 @@ static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (ld->ioctl) return ld->ioctl(ld, iod, cmd, arg); - mif_info("%s: ERR! cmd 0x%X not defined.\n", iod->name, cmd); + mif_info("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd); return -EINVAL; } + return 0; } @@ -1234,49 +1263,76 @@ static ssize_t misc_write(struct file *filp, const char __user *data, struct io_device *iod = (struct io_device *)filp->private_data; struct link_device *ld = get_current_link(iod); struct sk_buff *skb; + u8 *buff; int ret; - unsigned headroom = 0; - unsigned tailroom = 0; - size_t tx_size; - struct sipc5_frame_data frm; - struct timespec epoch; + size_t headroom; + size_t tailroom; + size_t tx_bytes; + u8 cfg; if (iod->format <= IPC_RFS && iod->id == 0) return -EINVAL; - headroom = tx_build_link_header(&frm, iod, ld, count); + cfg = sipc5_build_config(iod, ld, count); + + if (cfg) + headroom = sipc5_get_hdr_len(&cfg); + else + headroom = 0; if (ld->aligned) tailroom = sipc5_calc_padding_size(headroom + count); + else + tailroom = 0; - tx_size = headroom + count + tailroom; + tx_bytes = headroom + count + tailroom; - skb = alloc_skb(tx_size, GFP_KERNEL); + skb = alloc_skb(tx_bytes, GFP_KERNEL); if (!skb) { - mif_info("%s: ERR! alloc_skb fail (tx_size:%d)\n", - iod->name, tx_size); + mif_info("%s: ERR! alloc_skb fail (tx_bytes:%d)\n", + iod->name, tx_bytes); return -ENOMEM; } - /* store IPC link header*/ - memcpy(skb_put(skb, headroom), frm.hdr, headroom); + /* Build SIPC5 link header*/ + if (cfg) { + buff = skb_put(skb, headroom); + sipc5_build_header(iod, ld, buff, cfg, 0, count); + } - /* store IPC message */ - if (copy_from_user(skb_put(skb, count), data, count) != 0) { - if (skb) - dev_kfree_skb_any(skb); + /* Store IPC message */ + buff = skb_put(skb, count); + if (copy_from_user(buff, data, count)) { + mif_err("%s->%s: ERR! copy_from_user fail (count %d)\n", + iod->name, ld->name, count); + dev_kfree_skb_any(skb); return -EFAULT; } - if (iod->id == SIPC5_CH_ID_FMT_0) { + /* Apply padding */ + if (tailroom) + skb_put(skb, tailroom); + + if (iod->format == IPC_FMT) { + struct timespec epoch; + u8 *msg = (skb->data + headroom); +#if 0 + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: RIL2MIF", iod->mc->name); + pr_ipc(1, tag, msg, (count > 20 ? 20 : count)); +#endif getnstimeofday(&epoch); mif_time_log(iod->mc->msd, epoch, NULL, 0); - mif_ipc_log(MIF_IPC_RL2AP, iod->mc->msd, skb->data, skb->len); + mif_ipc_log(MIF_IPC_RL2AP, iod->mc->msd, msg, count); } - /* store padding */ - if (tailroom) - skb_put(skb, tailroom); +#if 0 + if (iod->format == IPC_RAMDUMP) { + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: DUMP2MIF", iod->name); + pr_ipc(1, tag, skb->data, (skb->len > 20 ? 20 : skb->len)); + } +#endif /* send data with sk_buff, link device will put sk_buff * into the specific sk_buff_q and run work-q to send data @@ -1286,13 +1342,15 @@ static ssize_t misc_write(struct file *filp, const char __user *data, ret = ld->send(ld, iod, skb); if (ret < 0) { - mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret); + mif_info("%s->%s: ERR! ld->send fail (err %d, tx_bytes %d)\n", + iod->name, ld->name, ret, tx_bytes); return ret; } - if (ret != tx_size) - mif_info("%s: wrong tx size (count:%d tx_size:%d ret:%d)\n", - iod->name, count, tx_size, ret); + if (ret != tx_bytes) { + mif_info("%s->%s: WARNING! ret %d != tx_bytes %d (count %d)\n", + iod->name, ld->name, ret, tx_bytes, count); + } return count; } @@ -1304,24 +1362,38 @@ static ssize_t misc_read(struct file *filp, char *buf, size_t count, struct sk_buff_head *rxq = &iod->sk_rx_q; struct sk_buff *skb; int copied = 0; - struct timespec epoch; - skb = skb_dequeue(rxq); - if (!skb) { + if (skb_queue_empty(rxq)) { mif_info("%s: ERR! no data in rxq\n", iod->name); return 0; } - if (iod->id == SIPC5_CH_ID_FMT_0) { + skb = skb_dequeue(rxq); + + if (iod->format == IPC_FMT) { + struct timespec epoch; +#if 0 + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: MIF2RIL", iod->mc->name); + pr_ipc(0, tag, skb->data, (skb->len > 20 ? 20 : skb->len)); +#endif getnstimeofday(&epoch); mif_time_log(iod->mc->msd, epoch, NULL, 0); mif_ipc_log(MIF_IPC_AP2RL, iod->mc->msd, skb->data, skb->len); } +#if 0 + if (iod->format == IPC_RAMDUMP) { + char tag[MIF_MAX_STR_LEN]; + snprintf(tag, MIF_MAX_STR_LEN, "%s: MIF2DUMP", iod->name); + pr_ipc(1, tag, skb->data, (skb->len > 20 ? 20 : skb->len)); + } +#endif + copied = skb->len > count ? count : skb->len; if (copy_to_user(buf, skb->data, copied)) { - mif_info("%s: ERR! copy_to_user fail\n", iod->name); + mif_err("%s: ERR! copy_to_user fail\n", iod->name); dev_kfree_skb_any(skb); return -EFAULT; } @@ -1339,43 +1411,6 @@ static ssize_t misc_read(struct file *filp, char *buf, size_t count, return copied; } -#ifdef CONFIG_LINK_DEVICE_C2C -static int misc_mmap(struct file *filp, struct vm_area_struct *vma) -{ - int r = 0; - unsigned long size = 0; - unsigned long pfn = 0; - unsigned long offset = 0; - struct io_device *iod = (struct io_device *)filp->private_data; - - if (!vma) - return -EFAULT; - - size = vma->vm_end - vma->vm_start; - offset = vma->vm_pgoff << PAGE_SHIFT; - if (offset + size > (C2C_CP_RGN_SIZE + C2C_SH_RGN_SIZE)) { - mif_info("ERR: offset + size > C2C_CP_RGN_SIZE\n"); - return -EINVAL; - } - - /* Set the noncacheable property to the region */ - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); - vma->vm_flags |= VM_RESERVED | VM_IO; - - pfn = __phys_to_pfn(C2C_CP_RGN_ADDR + offset); - r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); - if (r) { - mif_info("ERR: Failed in remap_pfn_range()!!!\n"); - return -EAGAIN; - } - - mif_info("%s: VA = 0x%08lx, offset = 0x%lx, size = %lu\n", - iod->name, vma->vm_start, offset, size); - - return 0; -} -#endif - static const struct file_operations misc_io_fops = { .owner = THIS_MODULE, .open = misc_open, @@ -1384,9 +1419,6 @@ static const struct file_operations misc_io_fops = { .unlocked_ioctl = misc_ioctl, .write = misc_write, .read = misc_read, -#ifdef CONFIG_LINK_DEVICE_C2C - .mmap = misc_mmap, -#endif }; static int vnet_open(struct net_device *ndev) @@ -1419,11 +1451,13 @@ static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) struct link_device *ld = get_current_link(iod); struct sk_buff *skb_new; int ret; - unsigned headroom = 0; - unsigned tailroom = 0; - unsigned long tx_bytes = skb->len; + unsigned headroom; + unsigned tailroom; + size_t count; + size_t tx_bytes; struct iphdr *ip_header = NULL; - struct sipc5_frame_data frm; + u8 *buff; + u8 cfg; /* When use `handover' with Network Bridge, * user -> bridge device(rmnet0) -> real rmnet(xxxx_rmnet0) -> here. @@ -1436,19 +1470,18 @@ static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) skb_pull(skb, sizeof(struct ethhdr)); } - headroom = tx_build_link_header(&frm, iod, ld, skb->len); + count = skb->len; - /* ip loop-back */ - ip_header = (struct iphdr *)skb->data; - if (iod->msd->loopback_ipaddr && - ip_header->daddr == iod->msd->loopback_ipaddr) { - swap(ip_header->saddr, ip_header->daddr); - frm.ch_id = DATA_LOOPBACK_CHANNEL; - frm.hdr[SIPC5_CH_ID_OFFSET] = DATA_LOOPBACK_CHANNEL; - } + cfg = sipc5_build_config(iod, ld, count); + + headroom = sipc5_get_hdr_len(&cfg); if (ld->aligned) - tailroom = sipc5_calc_padding_size(frm.len); + tailroom = sipc5_calc_padding_size(headroom + count); + else + tailroom = 0; + + tx_bytes = headroom + count + tailroom; if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) { mif_debug("%s: skb_copy_expand needed\n", iod->name); @@ -1463,7 +1496,18 @@ static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) skb_new = skb; } - memcpy(skb_push(skb_new, headroom), frm.hdr, headroom); + /* Build SIPC5 link header*/ + buff = skb_push(skb_new, headroom); + sipc5_build_header(iod, ld, buff, cfg, 0, count); + + /* IP loop-back */ + ip_header = (struct iphdr *)skb->data; + if (iod->msd->loopback_ipaddr && + ip_header->daddr == iod->msd->loopback_ipaddr) { + swap(ip_header->saddr, ip_header->daddr); + buff[SIPC5_CH_ID_OFFSET] = DATA_LOOPBACK_CHANNEL; + } + if (tailroom) skb_put(skb_new, tailroom); @@ -1473,12 +1517,18 @@ static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) ret = ld->send(ld, iod, skb_new); if (ret < 0) { netif_stop_queue(ndev); - mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret); + mif_info("%s->%s: ERR! ld->send fail (err %d, tx_bytes %d)\n", + iod->name, ld->name, ret, tx_bytes); return NETDEV_TX_BUSY; } + if (ret != tx_bytes) { + mif_info("%s->%s: WARNING! ret %d != tx_bytes %d (count %d)\n", + iod->name, ld->name, ret, tx_bytes, count); + } + ndev->stats.tx_packets++; - ndev->stats.tx_bytes += tx_bytes; + ndev->stats.tx_bytes += count; return NETDEV_TX_OK; } @@ -1581,16 +1631,19 @@ int sipc5_init_io_device(struct io_device *iod) ret = misc_register(&iod->miscdev); if (ret) mif_info("%s: ERR! misc_register fail\n", iod->name); + ret = device_create_file(iod->miscdev.this_device, &attr_waketime); if (ret) mif_info("%s: ERR! device_create_file fail\n", iod->name); + ret = device_create_file(iod->miscdev.this_device, &attr_loopback); if (ret) mif_err("failed to create `loopback file' : %s\n", iod->name); + ret = device_create_file(iod->miscdev.this_device, &attr_txlink); if (ret) |