/* /linux/drivers/misc/modem_if/modem_io_device.c * * Copyright (C) 2010 Google, Inc. * 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 #include #include #include #include #include #include #include #include #include #include #include #include "modem_prj.h" #define HDLC_START 0x7F #define HDLC_END 0x7E #define SIZE_OF_HDLC_START 1 #define SIZE_OF_HDLC_END 1 #define MAX_RXDATA_SIZE (4096 - 512) static const char hdlc_start[1] = { HDLC_START }; static const char hdlc_end[1] = { HDLC_END }; struct fmt_hdr { u16 len; u8 control; } __packed; struct raw_hdr { u32 len; u8 channel; u8 control; } __packed; struct rfs_hdr { u32 len; u8 cmd; u8 id; } __packed; static const char const *modem_state_name[] = { [STATE_OFFLINE] = "OFFLINE", [STATE_CRASH_EXIT] = "CRASH_EXIT", [STATE_BOOTING] = "BOOTING", [STATE_ONLINE] = "ONLINE", [STATE_LOADER_DONE] = "LOADER_DONE", [STATE_NV_REBUILDING] = "NV_REBUILDING", }; static int rx_iodev_skb(struct io_device *iod); static int get_header_size(struct io_device *iod) { switch (iod->format) { case IPC_FMT: return sizeof(struct fmt_hdr); case IPC_RAW: case IPC_MULTI_RAW: return sizeof(struct raw_hdr); case IPC_RFS: return sizeof(struct rfs_hdr); case IPC_BOOT: /* minimum size for transaction align */ return 4; case IPC_RAMDUMP: default: return 0; } } static int get_hdlc_size(struct io_device *iod, char *buf) { struct fmt_hdr *fmt_header; struct raw_hdr *raw_header; struct rfs_hdr *rfs_header; pr_debug("[MODEM_IF] buf : %02x %02x %02x (%d)\n", *buf, *(buf + 1), *(buf + 2), __LINE__); switch (iod->format) { case IPC_FMT: fmt_header = (struct fmt_hdr *)buf; return fmt_header->len; case IPC_RAW: case IPC_MULTI_RAW: raw_header = (struct raw_hdr *)buf; return raw_header->len; case IPC_RFS: rfs_header = (struct rfs_hdr *)buf; return rfs_header->len; default: break; } return 0; } static void *get_header(struct io_device *iod, size_t count, char *frame_header_buf) { struct fmt_hdr *fmt_h; struct raw_hdr *raw_h; struct rfs_hdr *rfs_h; switch (iod->format) { case IPC_FMT: fmt_h = (struct fmt_hdr *)frame_header_buf; fmt_h->len = count + sizeof(struct fmt_hdr); fmt_h->control = 0; return (void *)frame_header_buf; case IPC_RAW: case IPC_MULTI_RAW: raw_h = (struct raw_hdr *)frame_header_buf; raw_h->len = count + sizeof(struct raw_hdr); raw_h->channel = iod->id & 0x1F; raw_h->control = 0; return (void *)frame_header_buf; case IPC_RFS: rfs_h = (struct rfs_hdr *)frame_header_buf; rfs_h->len = count + sizeof(struct raw_hdr); rfs_h->id = iod->id; return (void *)frame_header_buf; default: return 0; } } static inline int rx_hdlc_head_start_check(char *buf) { /* check hdlc head and return size of start byte */ return (buf[0] == HDLC_START) ? SIZE_OF_HDLC_START : -EBADMSG; } static inline int rx_hdlc_tail_check(char *buf) { /* check hdlc tail and return size of tail byte */ return (buf[0] == HDLC_END) ? SIZE_OF_HDLC_END : -EBADMSG; } /* remove hdlc header and store IPC header */ static int rx_hdlc_head_check(struct io_device *iod, char *buf, unsigned rest) { struct header_data *hdr = &iod->h_data; int head_size = get_header_size(iod); int done_len = 0; int len = 0; struct modem_data *md = (struct modem_data *)\ iod->mc->dev->platform_data; /* first frame, remove start header 7F */ if (!hdr->start) { len = rx_hdlc_head_start_check(buf); if (len < 0) { pr_err("[MODEM_IF] Wrong HDLC start: 0x%x(%s)\n", *buf, iod->name); return len; /*Wrong hdlc start*/ } pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, __LINE__); /* set the start flag of current packet */ hdr->start = HDLC_START; hdr->len = 0; buf += len; done_len += len; rest -= len; /* rest, call by value */ } pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, __LINE__); /* store the IPC header to iod priv */ if (hdr->len < head_size) { len = min(rest, head_size - hdr->len); memcpy(hdr->hdr + hdr->len, buf, len); /* Skip the dummy byte inserted for 2-byte alignment in header. RAW format header size is 6 bytes. Start + 6 + 1 (skip byte) */ if (md->align == 1) { if ((iod->format == IPC_RAW || iod->format == IPC_MULTI_RAW) && (iod->net_typ == CDMA_NETWORK) && !(len & 0x01)) len++; } hdr->len += len; done_len += len; } pr_debug("[MODEM_IF] check done_len : %d, rest : %d (%d)\n", done_len, rest, __LINE__); return done_len; } /* alloc skb and copy dat to skb */ static int rx_hdlc_data_check(struct io_device *iod, char *buf, unsigned rest) { struct header_data *hdr = &iod->h_data; struct sk_buff *skb = iod->skb_recv; int head_size = get_header_size(iod); int data_size = get_hdlc_size(iod, hdr->hdr) - head_size; int alloc_size = min(data_size, MAX_RXDATA_SIZE); int len; int done_len = 0; int rest_len = data_size - hdr->flag_len; /* first payload data - alloc skb */ if (!skb) { switch (iod->format) { case IPC_RFS: alloc_size = min(data_size + head_size, \ MAX_RXDATA_SIZE); skb = alloc_skb(alloc_size, GFP_ATOMIC); if (unlikely(!skb)) return -ENOMEM; /* copy the RFS haeder to skb->data */ memcpy(skb_put(skb, head_size), hdr->hdr, head_size); break; case IPC_MULTI_RAW: if (data_size > MAX_RXDATA_SIZE) { \ pr_err("%s: %s: packet size too large (%d)\n",\ __func__, iod->name, data_size); return -EINVAL; } if (iod->net_typ == UMTS_NETWORK) skb = alloc_skb(alloc_size, GFP_ATOMIC); else skb = alloc_skb(alloc_size + sizeof(struct ethhdr), GFP_ATOMIC); if (unlikely(!skb)) return -ENOMEM; if (iod->net_typ != UMTS_NETWORK) skb_reserve(skb, sizeof(struct ethhdr)); break; default: skb = alloc_skb(alloc_size, GFP_ATOMIC); if (unlikely(!skb)) return -ENOMEM; break; } iod->skb_recv = skb; } while (rest > 0) { len = min(rest, alloc_size - skb->len); len = min(len, rest_len); memcpy(skb_put(skb, len), buf, len); buf += len; done_len += len; hdr->flag_len += len; rest -= len; rest_len -= len; if (!rest_len || !rest) break; rx_iodev_skb(iod); iod->skb_recv = NULL; alloc_size = min(rest_len, MAX_RXDATA_SIZE); skb = alloc_skb(alloc_size, GFP_ATOMIC); if (unlikely(!skb)) return -ENOMEM; iod->skb_recv = skb; } return done_len; } static int rx_iodev_skb_raw(struct io_device *iod) { int err; struct sk_buff *skb = iod->skb_recv; struct net_device *ndev; struct iphdr *ip_header; struct ethhdr *ehdr; const char source[ETH_ALEN] = SOURCE_MAC_ADDR; switch (iod->io_typ) { case IODEV_MISC: skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); wake_up(&iod->wq); return 0; case IODEV_NET: ndev = iod->ndev; if (!ndev) return NET_RX_DROP; skb->dev = ndev; ndev->stats.rx_packets++; ndev->stats.rx_bytes += skb->len; /* check the version of IP */ ip_header = (struct iphdr *)skb->data; if (ip_header->version == IP6VERSION) skb->protocol = htons(ETH_P_IPV6); else skb->protocol = htons(ETH_P_IP); if (iod->net_typ == UMTS_NETWORK) { skb_reset_mac_header(skb); } else { ehdr = (void *)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_NONE; skb_reset_mac_header(skb); skb_pull(skb, sizeof(struct ethhdr)); } err = netif_rx_ni(skb); if (err != NET_RX_SUCCESS) dev_err(&ndev->dev, "rx error: %d\n", err); return err; default: pr_err("[MODEM_IF] wrong io_type : %d\n", iod->io_typ); return -EINVAL; } } static void rx_iodev_work(struct work_struct *work) { int ret; struct sk_buff *skb; struct io_device *real_iod; struct io_device *iod = container_of(work, struct io_device, rx_work.work); skb = skb_dequeue(&iod->sk_rx_q); while (skb) { real_iod = *((struct io_device **)skb->cb); real_iod->skb_recv = skb; ret = rx_iodev_skb_raw(real_iod); if (ret == NET_RX_DROP) { pr_err("[MODEM_IF] %s: queue delayed work!\n", __func__); skb_queue_head(&iod->sk_rx_q, skb); schedule_delayed_work(&iod->rx_work, msecs_to_jiffies(20)); break; } else if (ret < 0) dev_kfree_skb_any(skb); skb = skb_dequeue(&iod->sk_rx_q); } } static int rx_multipdp(struct io_device *iod) { u8 ch; struct raw_hdr *raw_header = (struct raw_hdr *)&iod->h_data.hdr; struct io_raw_devices *io_raw_devs = (struct io_raw_devices *)iod->private_data; struct io_device *real_iod; ch = raw_header->channel; real_iod = io_raw_devs->raw_devices[ch]; if (!real_iod) { pr_err("[MODEM_IF] %s: wrong channel %d\n", __func__, ch); return -1; } *((struct io_device **)iod->skb_recv->cb) = real_iod; skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); pr_debug("sk_rx_qlen:%d\n", iod->sk_rx_q.qlen); schedule_delayed_work(&iod->rx_work, 0); return 0; } /* de-mux function draft */ static int rx_iodev_skb(struct io_device *iod) { switch (iod->format) { case IPC_MULTI_RAW: return rx_multipdp(iod); case IPC_FMT: case IPC_RFS: default: skb_queue_tail(&iod->sk_rx_q, iod->skb_recv); pr_debug("[MODEM_IF] wake up fmt,rfs skb\n"); wake_up(&iod->wq); return 0; } } static int rx_hdlc_packet(struct io_device *iod, const char *data, unsigned recv_size) { unsigned rest = recv_size; char *buf = (char *)data; int err = 0; int len; struct modem_data *md = (struct modem_data *)\ iod->mc->dev->platform_data; if (rest <= 0) goto exit; pr_debug("[MODEM_IF] RX_SIZE=%d\n", rest); if (iod->h_data.flag_len) goto data_check; next_frame: err = len = rx_hdlc_head_check(iod, buf, rest); if (err < 0) goto exit; /* buf++; rest--; goto next_frame; */ pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, __LINE__); buf += len; rest -= len; if (rest <= 0) goto exit; data_check: err = len = rx_hdlc_data_check(iod, buf, rest); if (err < 0) goto exit; pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, __LINE__); /* If the lenght of actual data is odd. Skip the dummy bit*/ if (md->align == 1) { if ((iod->format == IPC_RAW || iod->format == IPC_MULTI_RAW) && (iod->net_typ == CDMA_NETWORK) && (len & 0x01)) len++; } buf += len; rest -= len; if (!rest && iod->h_data.flag_len) return 0; else if (rest <= 0) goto exit; err = len = rx_hdlc_tail_check(buf); if (err < 0) { pr_err("[MODEM_IF] Wrong HDLC end: 0x%x(%s)\n", *buf, iod->name); goto exit; } pr_debug("[MODEM_IF] check len : %d, rest : %d (%d)\n", len, rest, __LINE__); /* Skip the dummy byte inserted for 2-byte alignment in header. Ox7E 00.*/ if (md->align == 1) { if ((iod->format == IPC_RAW || iod->format == IPC_MULTI_RAW) && (iod->net_typ == CDMA_NETWORK)) len++; } buf += len; rest -= len; if (rest < 0) goto exit; err = rx_iodev_skb(iod); if (err < 0) goto exit; /* initialize header & skb */ iod->skb_recv = NULL; memset(&iod->h_data, 0x00, sizeof(struct header_data)); if (rest) goto next_frame; exit: /* free buffers. mipi-hsi re-use recv buf */ if (rest < 0) err = -ERANGE; if (err < 0) { /* clear headers */ memset(&iod->h_data, 0x00, sizeof(struct header_data)); if (iod->skb_recv) { dev_kfree_skb_any(iod->skb_recv); iod->skb_recv = NULL; } } return err; } /* 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, const char *data, unsigned int len) { struct sk_buff *skb; int err; switch (iod->format) { case IPC_FMT: case IPC_RAW: case IPC_RFS: case IPC_MULTI_RAW: err = rx_hdlc_packet(iod, data, len); if (err < 0) pr_err("[MODEM_IF] fail process hdlc fram\n"); return err; case IPC_CMD: /* TODO- handle flow control command from CP */ return 0; case IPC_BOOT: case IPC_RAMDUMP: /* save packet to sk_buff */ skb = alloc_skb(len, GFP_ATOMIC); if (!skb) { pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); return -ENOMEM; } pr_debug("[MODEM_IF] boot len : %d\n", len); memcpy(skb_put(skb, len), data, len); skb_queue_tail(&iod->sk_rx_q, skb); pr_debug("[MODEM_IF] skb len : %d\n", skb->len); wake_up(&iod->wq); return len; default: 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) { iod->mc->phone_state = state; pr_info("[MODEM_IF] %s state changed: %s\n", \ iod->name, modem_state_name[state]); if ((state == STATE_CRASH_EXIT) || (state == STATE_NV_REBUILDING)) wake_up(&iod->wq); } static int misc_open(struct inode *inode, struct file *filp) { struct io_device *iod = to_io_device(filp->private_data); filp->private_data = (void *)iod; if (iod->format != IPC_BOOT && iod->format != IPC_RAMDUMP) pr_info("[MODEM_IF] misc_open : %s\n", iod->name); if (iod->link->init_comm) return iod->link->init_comm(iod->link, iod); return 0; } static int misc_release(struct inode *inode, struct file *filp) { struct io_device *iod = (struct io_device *)filp->private_data; if (iod->format != IPC_BOOT && iod->format != IPC_RAMDUMP) pr_info("[MODEM_IF] misc_release : %s\n", iod->name); if (iod->link->terminate_comm) iod->link->terminate_comm(iod->link, iod); skb_queue_purge(&iod->sk_rx_q); 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_EXIT) || (iod->mc->phone_state == STATE_NV_REBUILDING)) 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; pr_debug("[MODEM_IF] misc_ioctl : 0x%x\n", cmd); switch (cmd) { case IOCTL_MODEM_ON: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_ON\n"); return iod->mc->ops.modem_on(iod->mc); case IOCTL_MODEM_OFF: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_OFF\n"); return iod->mc->ops.modem_off(iod->mc); case IOCTL_MODEM_RESET: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_RESET\n"); return iod->mc->ops.modem_reset(iod->mc); case IOCTL_MODEM_FORCE_CRASH_EXIT: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n"); return iod->mc->ops.modem_force_crash_exit(iod->mc); case IOCTL_MODEM_DUMP_RESET: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_FORCE_CRASH_EXIT\n"); return iod->mc->ops.modem_dump_reset(iod->mc); case IOCTL_MODEM_BOOT_ON: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_ON\n"); return iod->mc->ops.modem_boot_on(iod->mc); case IOCTL_MODEM_BOOT_OFF: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_BOOT_OFF\n"); return iod->mc->ops.modem_boot_off(iod->mc); /* TODO - will remove this command after ril updated */ case IOCTL_MODEM_START: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_START\n"); return 0; case IOCTL_MODEM_STATUS: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_STATUS\n"); p_state = iod->mc->phone_state; if (p_state == STATE_NV_REBUILDING) { pr_info("[MODEM_IF] nv rebuild state : %d\n", p_state); iod->mc->phone_state = STATE_ONLINE; } return p_state; case IOCTL_MODEM_DUMP_START: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_DUMP_START\n"); return iod->link->dump_start(iod->link, iod); case IOCTL_MODEM_DUMP_UPDATE: pr_debug("[MODEM_IF] misc_ioctl : IOCTL_MODEM_DUMP_UPDATE\n"); return iod->link->dump_update(iod->link, iod, _arg); case IOCTL_MODEM_GOTA_START: pr_debug("[GOTA] misc_ioctl : IOCTL_MODEM_GOTA_START\n"); return iod->link->gota_start(iod->link, iod); case IOCTL_MODEM_FW_UPDATE: pr_debug("[GOTA] misc_ioctl : IOCTL_MODEM_FW_UPDATE\n"); return iod->link->modem_update(iod->link, iod, _arg); default: return -EINVAL; } } static ssize_t misc_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos) { struct io_device *iod = (struct io_device *)filp->private_data; int frame_len = 0; char frame_header_buf[sizeof(struct raw_hdr)]; struct sk_buff *skb; /* TODO - check here flow control for only raw data */ if (iod->format == IPC_BOOT || iod->format == IPC_RAMDUMP) frame_len = count + get_header_size(iod); else frame_len = count + SIZE_OF_HDLC_START + get_header_size(iod) + SIZE_OF_HDLC_END; skb = alloc_skb(frame_len, GFP_KERNEL); if (!skb) { pr_err("[MODEM_IF] fail alloc skb (%d)\n", __LINE__); return -ENOMEM; } switch (iod->format) { case IPC_BOOT: case IPC_RAMDUMP: if (copy_from_user(skb_put(skb, count), buf, count) != 0) { dev_kfree_skb_any(skb); return -EFAULT; } break; case IPC_RFS: memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, SIZE_OF_HDLC_START); if (copy_from_user(skb_put(skb, count), buf, count) != 0) { dev_kfree_skb_any(skb); return -EFAULT; } memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, SIZE_OF_HDLC_END); break; default: memcpy(skb_put(skb, SIZE_OF_HDLC_START), hdlc_start, SIZE_OF_HDLC_START); memcpy(skb_put(skb, get_header_size(iod)), get_header(iod, count, frame_header_buf), get_header_size(iod)); if (copy_from_user(skb_put(skb, count), buf, count) != 0) { dev_kfree_skb_any(skb); return -EFAULT; } memcpy(skb_put(skb, SIZE_OF_HDLC_END), hdlc_end, SIZE_OF_HDLC_END); break; } /* send data with sk_buff, link device will put sk_buff * into the specific sk_buff_q and run work-q to send data */ return iod->link->send(iod->link, iod, skb); } static ssize_t misc_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { struct io_device *iod = (struct io_device *)filp->private_data; struct sk_buff *skb; int pktsize = 0; skb = skb_dequeue(&iod->sk_rx_q); if (!skb) { printk_ratelimited(KERN_ERR "[MODEM_IF] no data from sk_rx_q, " "modem_state : %s(%s)\n", modem_state_name[iod->mc->phone_state], iod->name); return 0; } if (skb->len > count) { pr_err("[MODEM_IF] skb len is too big = %d,%d!(%d)\n", count, skb->len, __LINE__); dev_kfree_skb_any(skb); return -EIO; } pr_debug("[MODEM_IF] skb len : %d\n", skb->len); pktsize = skb->len; if (copy_to_user(buf, skb->data, pktsize) != 0) return -EIO; dev_kfree_skb_any(skb); pr_debug("[MODEM_IF] copy to user : %d\n", pktsize); return pktsize; } 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, }; static int vnet_open(struct net_device *ndev) { netif_start_queue(ndev); return 0; } static int vnet_stop(struct net_device *ndev) { netif_stop_queue(ndev); return 0; } static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) { int ret; struct raw_hdr hd; struct sk_buff *skb_new; struct vnet *vnet = netdev_priv(ndev); struct io_device *iod = vnet->iod; /* umts doesn't need to discard ethernet header */ if (iod->net_typ != UMTS_NETWORK) { if (iod->id >= PSD_DATA_CHID_BEGIN && iod->id <= PSD_DATA_CHID_END) skb_pull(skb, sizeof(struct ethhdr)); } hd.len = skb->len + sizeof(hd); hd.control = 0; hd.channel = iod->id & 0x1F; skb_new = skb_copy_expand(skb, sizeof(hd) + sizeof(hdlc_start), sizeof(hdlc_end), GFP_ATOMIC); if (!skb_new) { dev_kfree_skb_any(skb); return -ENOMEM; } memcpy(skb_push(skb_new, sizeof(hd)), &hd, sizeof(hd)); memcpy(skb_push(skb_new, sizeof(hdlc_start)), hdlc_start, sizeof(hdlc_start)); memcpy(skb_put(skb_new, sizeof(hdlc_end)), hdlc_end, sizeof(hdlc_end)); ret = iod->link->send(iod->link, iod, skb_new); if (ret < 0) { dev_kfree_skb_any(skb); return NETDEV_TX_BUSY; } ndev->stats.tx_packets++; ndev->stats.tx_bytes += skb->len; dev_kfree_skb_any(skb); 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 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; /* get data from link device */ iod->recv = io_dev_recv_data_from_link_dev; INIT_LIST_HEAD(&iod->list); /* register misc or net drv */ switch (iod->io_typ) { case IODEV_MISC: init_waitqueue_head(&iod->wq); skb_queue_head_init(&iod->sk_rx_q); INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work); iod->miscdev.minor = MISC_DYNAMIC_MINOR; iod->miscdev.name = iod->name; iod->miscdev.fops = &misc_io_fops; ret = misc_register(&iod->miscdev); if (ret) pr_err("failed to register misc io device : %s\n", iod->name); break; case IODEV_NET: if (iod->net_typ == UMTS_NETWORK) iod->ndev = alloc_netdev(0, iod->name, vnet_setup); else iod->ndev = alloc_netdev(0, iod->name, vnet_setup_ether); if (!iod->ndev) { pr_err("failed to alloc netdev\n"); return -ENOMEM; } ret = register_netdev(iod->ndev); if (ret) free_netdev(iod->ndev); pr_debug("%s: %d(iod:0x%p)\n", __func__, __LINE__, iod); vnet = netdev_priv(iod->ndev); pr_debug("%s: %d(vnet:0x%p)\n", __func__, __LINE__, vnet); vnet->iod = iod; break; case IODEV_DUMMY: skb_queue_head_init(&iod->sk_rx_q); INIT_DELAYED_WORK(&iod->rx_work, rx_iodev_work); break; default: pr_err("wrong io_type : %d\n", iod->io_typ); return -EINVAL; } pr_debug("[MODEM_IF] %s(%d) : init_io_device() done : %d\n", iod->name, iod->io_typ, ret); return ret; }