diff options
Diffstat (limited to 'drivers/misc/modem_if/sipc5_io_device.c')
-rw-r--r-- | drivers/misc/modem_if/sipc5_io_device.c | 1620 |
1 files changed, 1620 insertions, 0 deletions
diff --git a/drivers/misc/modem_if/sipc5_io_device.c b/drivers/misc/modem_if/sipc5_io_device.c new file mode 100644 index 00000000000..9e22e9cb718 --- /dev/null +++ b/drivers/misc/modem_if/sipc5_io_device.c @@ -0,0 +1,1620 @@ +/* /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 + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/if_arp.h> +#include <linux/ip.h> +#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_prj.h" +#include "modem_utils.h" + +static ssize_t show_waketime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int msec; + char *p = buf; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + msec = jiffies_to_msecs(iod->waketime); + + p += sprintf(buf, "raw waketime : %ums\n", msec); + + return p - buf; +} + +static ssize_t store_waketime(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long msec; + int ret; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + ret = strict_strtoul(buf, 10, &msec); + if (ret) + return count; + + iod->waketime = msecs_to_jiffies(msec); + + return count; +} + +static struct device_attribute attr_waketime = + __ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime); + +static ssize_t show_loopback(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr; + char *p = buf; + + p += sprintf(buf, "%u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]); + + return p - buf; +} + +static ssize_t store_loopback(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + + msd->loopback_ipaddr = ipv4str_to_be32(buf, count); + + return count; +} + +static struct device_attribute attr_loopback = + __ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback); + +static void iodev_showtxlink(struct io_device *iod, void *args) +{ + char **p = (char **)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); +} + +static ssize_t show_txlink(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + char *p = buf; + + iodevs_for_each(msd, iodev_showtxlink, &p); + + return p - buf; +} + +static ssize_t store_txlink(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + /* don't change without gpio dynamic switching */ + return -EINVAL; +} + +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) +{ + 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; + } + } else { + frm->hdr_len = SIPC5_MIN_HEADER_SIZE; + } + + 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); +} + +/** + * 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) +{ + u8 *buff = frm->hdr; + u16 *sz16 = (u16 *)(buff + SIPC5_LEN_OFFSET); + u32 *sz32 = (u32 *)(buff + SIPC5_LEN_OFFSET); + + memset(frm, 0, sizeof(struct sipc5_frame_data)); + + if (iod->format == IPC_CMD || + iod->format == IPC_BOOT || + iod->format == IPC_RAMDUMP) { + frm->len = count; + return 0; + } + + frm->config = SIPC5_START_MASK; + + if (iod->format == IPC_FMT && count > 2048) { + frm->ctl_fld = true; + frm->config |= SIPC5_EXT_FIELD_EXIST; + frm->config |= SIPC5_CTL_FIELD_EXIST; + } + + if (iod->id >= SIPC5_CH_ID_RFS_0 && count > 0xFFFF) { + frm->ext_len = true; + frm->config |= SIPC5_EXT_FIELD_EXIST; + } + + 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; +} + +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 sk_buff *rx_skb; + u8 ctrl = skbpriv(skb)->control; + unsigned id = ctrl & 0x7F; + + 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); + } + + wake_up(&iod->wq); + return 0; + } + + /* + ** 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); + + rx_skb = rx_alloc_skb(fh->len, iod, ld); + if (!rx_skb) { + mif_info("%s: ERR! rx_alloc_skb fail\n", iod->name); + return -ENOMEM; + } + + iod->skb[id] = rx_skb; + } else { + rx_skb = iod->skb[id]; + } + + /* + ** Start 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); + } 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); + } + + iod->skb[id] = NULL; + wake_up(&iod->wq); + } + + 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; + + 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_multi_pdp(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; /* same with real_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) { + mif_info("%s: ERR! no iod->ndev\n", iod->name); + return -ENODEV; + } + + skb->dev = ndev; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + /* check the version of IP */ + iphdr = (struct iphdr *)skb->data; + if (iphdr->version == IP6VERSION) + skb->protocol = htons(ETH_P_IPV6); + else + skb->protocol = htons(ETH_P_IP); + + if (iod->use_handover) { + skb_push(skb, sizeof(struct ethhdr)); + ehdr = (void *)skb->data; + 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)); + } + + if (in_interrupt()) + ret = netif_rx(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); + + 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; + + if (unlikely(ch == SIPC5_CH_ID_MAX || ch == 0)) { + mif_info("%s: ERR! invalid ch# %d\n", link, ch); + return -ENODEV; + } + + /* 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); + 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)) + return rx_loopback(skb); + + if (atomic_read(&iod->opened) <= 0) { + mif_info("%s: ERR! %s is not opened\n", link, iod->name); + return -ENODEV; + } + + if (ch >= SIPC5_CH_ID_RFS_0) + return rx_rfs_frame(skb); + else if (ch >= SIPC5_CH_ID_FMT_0) + return rx_fmt_frame(skb); + else if (iod->io_typ == IODEV_MISC) + return rx_raw_misc(skb); + else + 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) +{ + 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); + } + + 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; + } + + 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->data_rcvd += len; + + mif_debug("%s: FRM data_len:%d, data_rcvd:%d\n", + link, frm->data_len, frm->data_rcvd); + + return len; +} + +static int rx_frame_from_serial(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 err = 0; + int done = 0; + + mif_debug("%s: size = %d\n", link, size); + + 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; + } + +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; + + if (rest == 0) + return size; + +recv_data: + err = 0; + + 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; + + mif_debug("%s: rx_payload() -> done:%d rest:%d\n", link, done, rest); + + 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; + } + + /* At this point, one complete link layer frame has been received. */ + + /* 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; + } + + 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)); + + 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); + } + return err; + +err_range: + mif_info("%s: ERR! size:%d vs. rest:%d\n", link, size, rest); + return size; +} + +/** + * 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 + * + * 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 + * + * Returns SIPC5 header length + */ +static int rx_header_from_mem(struct link_device *ld, u8 *buff, unsigned rest, + struct sipc5_frame_data *frm) +{ + char *link = ld->name; + u8 cfg = buff[0]; + + /* 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); + + /* Store the link layer header to the header buffer */ + memcpy(frm->hdr, buff, frm->hdr_len); + frm->hdr_rcvd = frm->hdr_len; + + /* Build and store the meta data of this frame */ + rx_build_meta_data(ld, frm); + + /* Verify frame length */ + if (unlikely(frm->len > rest)) { + mif_info("%s: ERR! frame length %d > rest %d\n", + link, frm->len, rest); + return -EBADMSG; + } + + return frm->hdr_rcvd; +} + +/* copy data to skb */ +static int rx_payload_from_mem(struct sk_buff *skb, u8 *buff, unsigned len) +{ + /* If there is no skb, data must be dropped. */ + if (skb) + memcpy(skb_put(skb, len), buff, len); + return len; +} + +static int rx_frame_from_mem(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; + + mif_debug("%s: size = %d\n", link, size); + + while (rest > 0) { + /* Initialize the frame data buffer */ + memset(frm, 0, sizeof(struct sipc5_frame_data)); + skb = NULL; + + /* Receive and analyze link layer header */ + done = rx_header_from_mem(ld, buff, rest, frm); + if (unlikely(done < 0)) + return -EBADMSG; + + /* Verify rest size */ + rest -= done; + if (rest < 0) { + mif_info("%s: ERR! rx_header -> rest %d\n", link, rest); + return -ERANGE; + } + + /* Move buff pointer to the payload */ + buff += done; + + /* Prepare an akb */ + len = frm->data_len; + skb = rx_alloc_skb(len, iod, ld); + + /* Store channel ID and control fields to the CB of the skb */ + skbpriv(skb)->ch_id = frm->ch_id; + skbpriv(skb)->control = frm->control; + + /* 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; + + /* 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; + } + buff += done; + rest -= done; + } + + 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); + } + } + + return 0; +} + +/* 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: + case IPC_RFS: + case IPC_MULTI_RAW: + 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); + + return err; + + case IPC_CMD: + case IPC_BOOT: + case IPC_RAMDUMP: + /* save packet to sk_buff */ + skb = rx_alloc_skb(len, iod, ld); + if (!skb) { + mif_info("%s: ERR! rx_alloc_skb fail\n", link); + 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); + } + 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, + struct sk_buff *skb) +{ + struct sipc5_frame_data *frm = &fragdata(iod, ld)->f_data; + u8 cfg = skb->data[0]; + + /* Initialize the frame data buffer */ + memset(frm, 0, sizeof(struct sipc5_frame_data)); + + /* + ** The start of a link layer header has already been checked in the + ** link device. + */ + + /* Analyze the configuration of the link layer header */ + rx_check_frame_cfg(cfg, frm); + + /* Store the link layer header to the header buffer */ + memcpy(frm->hdr, skb->data, frm->hdr_len); + frm->hdr_rcvd = frm->hdr_len; + + /* Build and store the meta data of this frame */ + rx_build_meta_data(ld, frm); + + /* + ** The length of the frame has already been checked in the link device. + */ + + /* Trim the link layer header off the frame */ + skb_pull(skb, frm->hdr_len); + + /* Store channel ID and control fields to the CB of the skb */ + skbpriv(skb)->ch_id = frm->ch_id; + skbpriv(skb)->control = frm->control; + + /* Demux the frame */ + if (rx_demux(ld, skb) < 0) { + mif_err("%s: ERR! rx_demux fail\n", ld->name); + return -EINVAL; + } + + return 0; +} + +/* 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; + + switch (dev) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + case IPC_MULTI_RAW: + if (iod->waketime) + wake_lock_timeout(&iod->wakelock, iod->waketime); + + err = rx_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); + } + + return err; + + default: + mif_info("%s: ERR! unknown device %d\n", link, dev); + return -EINVAL; + } +} + +/* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ +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); + + iod->mc->phone_state = state; + + if (state == STATE_CRASH_RESET || state == STATE_CRASH_EXIT || + state == STATE_NV_REBUILDING) + wake_up(&iod->wq); +} + +/** + * io_dev_sim_state_changed + * @iod: IPC's io_device + * @sim_online: SIM is online? + */ +static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online) +{ + if (atomic_read(&iod->opened) == 0) { + mif_info("%s: ERR! not opened\n", iod->name); + } else if (iod->mc->sim_state.online == sim_online) { + mif_info("%s: SIM state not changed\n", iod->name); + } else { + iod->mc->sim_state.online = sim_online; + iod->mc->sim_state.changed = true; + mif_info("%s: SIM state changed {online %d, changed %d}\n", + iod->name, iod->mc->sim_state.online, + iod->mc->sim_state.changed); + wake_up(&iod->wq); + } +} + +static void iodev_dump_status(struct io_device *iod, void *args) +{ + if (iod->format == IPC_RAW && iod->io_typ == IODEV_NET) { + struct link_device *ld = get_current_link(iod); + mif_com_log(iod->mc->msd, "%s: %s\n", iod->name, ld->name); + } +} + +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 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); + return ret; + } + } + } + + mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened)); + + return 0; +} + +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; + + atomic_dec(&iod->opened); + skb_queue_purge(&iod->sk_rx_q); + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->terminate_comm) + ld->terminate_comm(ld, iod); + } + + mif_err("%s (opened %d)\n", iod->name, atomic_read(&iod->opened)); + + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + + poll_wait(filp, &iod->wq, wait); + + if (!skb_queue_empty(&iod->sk_rx_q) && + iod->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 (iod->format == IPC_RAW) { + msleep(20); + return 0; + } + return POLLHUP; + } else { + return 0; + } +} + +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 "; + 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); + + case IOCTL_MODEM_OFF: + mif_info("%s: IOCTL_MODEM_OFF\n", iod->name); + return iod->mc->ops.modem_off(iod->mc); + + case IOCTL_MODEM_RESET: + mif_info("%s: IOCTL_MODEM_RESET\n", iod->name); + return iod->mc->ops.modem_reset(iod->mc); + + case IOCTL_MODEM_BOOT_ON: + mif_info("%s: IOCTL_MODEM_BOOT_ON\n", iod->name); + return iod->mc->ops.modem_boot_on(iod->mc); + + case IOCTL_MODEM_BOOT_OFF: + mif_info("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name); + return iod->mc->ops.modem_boot_off(iod->mc); + + 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; + + case IOCTL_MODEM_STATUS: + mif_debug("%s: IOCTL_MODEM_STATUS\n", iod->name); + + p_state = iod->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 ? + STATE_SIM_ATTACH : STATE_SIM_DETACH; + iod->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; + } + 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; + + iodevs_for_each(iod->msd, iodev_netif_stop, 0); + return 0; + + case IOCTL_MODEM_PROTOCOL_RESUME: + mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n", + iod->name); + + if (iod->format != IPC_MULTI_RAW) + return -EINVAL; + + iodevs_for_each(iod->msd, iodev_netif_wake, 0); + return 0; + + case IOCTL_MODEM_DUMP_START: + mif_info("%s: IOCTL_MODEM_DUMP_START\n", iod->name); + return ld->dump_start(ld, iod); + + case IOCTL_MODEM_DUMP_UPDATE: + mif_debug("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name); + return ld->dump_update(ld, iod, arg); + + 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); + 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); + 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_MIF_LOG_DUMP: + iodevs_for_each(iod->msd, iodev_dump_status, 0); + size = MAX_MIF_BUFF_SIZE; + ret = copy_to_user((void __user *)arg, &size, + sizeof(unsigned long)); + if (ret < 0) + return -EFAULT; + + mif_dump_log(iod->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 + * It will be call for specific link ioctl */ + if (ld->ioctl) + return ld->ioctl(ld, iod, cmd, arg); + + mif_info("%s: ERR! cmd 0x%X not defined.\n", iod->name, cmd); + return -EINVAL; + } + return 0; +} + +static ssize_t misc_write(struct file *filp, const char __user *data, + size_t count, loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + struct sk_buff *skb; + int ret; + unsigned headroom = 0; + unsigned tailroom = 0; + size_t tx_size; + struct sipc5_frame_data frm; + + if (iod->format <= IPC_RFS && iod->id == 0) + return -EINVAL; + + headroom = tx_build_link_header(&frm, iod, ld, count); + + if (ld->aligned) + tailroom = sipc5_calc_padding_size(headroom + count); + + tx_size = headroom + count + tailroom; + + skb = alloc_skb(tx_size, GFP_KERNEL); + if (!skb) { + mif_info("%s: ERR! alloc_skb fail (tx_size:%d)\n", + iod->name, tx_size); + return -ENOMEM; + } + + /* store IPC link header*/ + memcpy(skb_put(skb, headroom), frm.hdr, headroom); + + /* store IPC message */ + if (copy_from_user(skb_put(skb, count), data, count) != 0) { + if (skb) + dev_kfree_skb_any(skb); + return -EFAULT; + } + + if (iod->format == IPC_FMT) { + struct timespec epoch; + u8 *msg = (skb->data + headroom); +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: RL2MIF", iod->mc->name); + pr_ipc(str, msg, (count > 16 ? 16 : count)); +#endif + getnstimeofday(&epoch); + mif_time_log(iod->mc->msd, epoch, NULL, 0); + mif_ipc_log(MIF_IPC_RL2AP, iod->mc->msd, msg, count); + } + + /* store padding */ + if (tailroom) + skb_put(skb, tailroom); + + /* send data with sk_buff, link device will put sk_buff + * into the specific sk_buff_q and run work-q to send data + */ + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + ret = ld->send(ld, iod, skb); + if (ret < 0) { + mif_info("%s: ERR! ld->send fail (err %d)\n", iod->name, ret); + 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); + + return count; +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sk_buff *skb; + int copied = 0; + + skb = skb_dequeue(rxq); + if (!skb) { + mif_info("%s: ERR! no data in rxq\n", iod->name); + return 0; + } + + if (iod->format == IPC_FMT) { + struct timespec epoch; +#ifndef CONFIG_SAMSUNG_PRODUCT_SHIP + char str[MIF_MAX_STR_LEN]; + snprintf(str, MIF_MAX_STR_LEN, "%s: MIF2RL", iod->mc->name); + pr_ipc(str, skb->data, (skb->len > 16 ? 16 : 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); + } + + 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); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + mif_debug("%s: data:%d copied:%d qlen:%d\n", + iod->name, skb->len, copied, rxq->qlen); + + if (skb->len > count) { + skb_pull(skb, count); + skb_queue_head(rxq, skb); + } else { + dev_kfree_skb_any(skb); + } + + 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, + .release = misc_release, + .poll = misc_poll, + .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) +{ + struct vnet *vnet = netdev_priv(ndev); + + mif_err("%s\n", vnet->iod->name); + + netif_start_queue(ndev); + atomic_inc(&vnet->iod->opened); + return 0; +} + +static int vnet_stop(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + + mif_err("%s\n", vnet->iod->name); + + atomic_dec(&vnet->iod->opened); + netif_stop_queue(ndev); + skb_queue_purge(&vnet->iod->sk_rx_q); + return 0; +} + +static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + struct io_device *iod = vnet->iod; + 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; + struct iphdr *ip_header = NULL; + struct sipc5_frame_data frm; + + /* When use `handover' with Network Bridge, + * user -> bridge device(rmnet0) -> real rmnet(xxxx_rmnet0) -> here. + * bridge device is ethernet device unlike xxxx_rmnet(net device). + * We remove the an ethernet header of skb before using skb->len, + * because bridge device added an ethernet header to skb. + */ + if (iod->use_handover) { + if (iod->id >= PS_DATA_CH_0 && iod->id <= PS_DATA_CH_LAST) + skb_pull(skb, sizeof(struct ethhdr)); + } + + headroom = tx_build_link_header(&frm, iod, ld, 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; + } + + if (ld->aligned) + tailroom = sipc5_calc_padding_size(frm.len); + + if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) { + mif_debug("%s: skb_copy_expand needed\n", iod->name); + skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC); + /* skb_copy_expand success or not, free old skb from caller */ + dev_kfree_skb_any(skb); + if (!skb_new) { + mif_info("%s: ERR! skb_copy_expand fail\n", iod->name); + return NETDEV_TX_BUSY; + } + } else { + skb_new = skb; + } + + memcpy(skb_push(skb_new, headroom), frm.hdr, headroom); + if (tailroom) + skb_put(skb_new, tailroom); + + skbpriv(skb_new)->iod = iod; + skbpriv(skb_new)->ld = ld; + + 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); + return NETDEV_TX_BUSY; + } + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += tx_bytes; + + return NETDEV_TX_OK; +} + +static struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, + .ndo_start_xmit = vnet_xmit, +}; + +static void vnet_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->addr_len = 0; + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +static void vnet_setup_ether(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_ETHER; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_SLAVE; + ndev->addr_len = ETH_ALEN; + random_ether_addr(ndev->dev_addr); + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +int sipc5_init_io_device(struct io_device *iod) +{ + int ret = 0; + struct vnet *vnet; + + /* Get modem state from modem control device */ + iod->modem_state_changed = io_dev_modem_state_changed; + + iod->sim_state_changed = io_dev_sim_state_changed; + + /* Get data from link device */ + mif_debug("%s: SIPC version = %d\n", iod->name, iod->ipc_version); + iod->recv = io_dev_recv_data_from_link_dev; + iod->recv_skb = io_dev_recv_skb_from_link_dev; + + /* Register misc or net device */ + switch (iod->io_typ) { + case IODEV_MISC: + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_info("%s: ERR! misc_register failed\n", iod->name); + + break; + + case IODEV_NET: + skb_queue_head_init(&iod->sk_rx_q); + if (iod->use_handover) + iod->ndev = alloc_netdev(0, iod->name, + vnet_setup_ether); + else + iod->ndev = alloc_netdev(0, iod->name, vnet_setup); + + if (!iod->ndev) { + mif_info("%s: ERR! alloc_netdev fail\n", iod->name); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) { + mif_info("%s: ERR! register_netdev fail\n", iod->name); + free_netdev(iod->ndev); + } + + mif_debug("iod 0x%p\n", iod); + vnet = netdev_priv(iod->ndev); + mif_debug("vnet 0x%p\n", vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + 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) + mif_err("failed to create `txlink file' : %s\n", + iod->name); + break; + + default: + mif_info("%s: ERR! wrong io_type %d\n", iod->name, iod->io_typ); + return -EINVAL; + } + + return ret; +} + |