aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/modem_if_na/modem_io_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/modem_if_na/modem_io_device.c')
-rw-r--r--drivers/misc/modem_if_na/modem_io_device.c965
1 files changed, 965 insertions, 0 deletions
diff --git a/drivers/misc/modem_if_na/modem_io_device.c b/drivers/misc/modem_if_na/modem_io_device.c
new file mode 100644
index 0000000..1a936b1
--- /dev/null
+++ b/drivers/misc/modem_if_na/modem_io_device.c
@@ -0,0 +1,965 @@
+/* /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 <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/ratelimit.h>
+
+#include <linux/platform_data/modem_na.h>
+#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;
+}