diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/phone_svn/svnet | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/phone_svn/svnet')
-rw-r--r-- | drivers/phone_svn/svnet/Makefile | 5 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/main.c | 867 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/main.h | 5 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/pdp.c | 100 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/pdp.h | 35 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/sipc.h | 64 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/sipc4.c | 2119 | ||||
-rw-r--r-- | drivers/phone_svn/svnet/sipc4.h | 267 |
8 files changed, 3462 insertions, 0 deletions
diff --git a/drivers/phone_svn/svnet/Makefile b/drivers/phone_svn/svnet/Makefile new file mode 100644 index 0000000..42fad44 --- /dev/null +++ b/drivers/phone_svn/svnet/Makefile @@ -0,0 +1,5 @@ +svnet-y := main.o pdp.o + +svnet-y += sipc4.o + +obj-$(CONFIG_PHONE_SVNET) += svnet.o diff --git a/drivers/phone_svn/svnet/main.c b/drivers/phone_svn/svnet/main.c new file mode 100644 index 0000000..2db9412 --- /dev/null +++ b/drivers/phone_svn/svnet/main.c @@ -0,0 +1,867 @@ +/** + * Samsung Virtual Network driver using OneDram device + * + * Copyright (C) 2010 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/*#define DEBUG */ + +#if defined(DEBUG) +#define NOISY_DEBUG +#endif + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/list.h> +#include <linux/jiffies.h> + +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <linux/if.h> +#include <linux/if_arp.h> + +#include <linux/if_phonet.h> +#include <linux/phonet.h> +#include <net/phonet/phonet.h> + +#ifdef CONFIG_HAS_WAKELOCK +#include <linux/wakelock.h> + +#define DEFAULT_RAW_WAKE_TIME (6*HZ) +#define DEFAULT_FMT_WAKE_TIME (HZ/2) +#endif + +#if defined(NOISY_DEBUG) +# define _dbg(dev, format, arg...) dev_dbg(dev, format, ## arg) +#else +# define _dbg(dev, format, arg...) do { } while (0) +#endif + +#include "sipc.h" +#include "pdp.h" + +#define SVNET_DEV_ADDR 0xa0 + +enum { + SVNET_NORMAL = 0, + SVNET_RESET, + SVNET_EXIT, + SVNET_MAX, +}; + +struct svnet_stat { + unsigned int st_wq_state; + unsigned long st_recv_evt; + unsigned long st_recv_pkt_ph; + unsigned long st_recv_pkt_pdp; + unsigned long st_do_write; + unsigned long st_do_read; + unsigned long st_do_rx; +}; +static struct svnet_stat stat; + +struct svnet_evt { + struct list_head list; + u32 event; +}; + +struct svnet_evt_head { + struct list_head list; + u32 len; + spinlock_t lock; +}; + +struct svnet { + struct net_device *ndev; + const struct attribute_group *group; + + struct workqueue_struct *wq; + struct work_struct work_read; + struct delayed_work work_write; + struct delayed_work work_rx; + + struct work_struct work_exit; + int exit_flag; + + struct sk_buff_head txq; + struct svnet_evt_head rxq; + + struct sipc *si; +#ifdef CONFIG_HAS_WAKELOCK + struct wake_lock wlock; + long wake_time; /* jiffies */ /* wake time for not fmt packet */ + long wake_process_time; /* jiffies */ /* processing wake time */ +#endif +}; + +static struct svnet *svnet_dev; + +#ifdef CONFIG_HAS_WAKELOCK +static inline void _wake_lock_init(struct svnet *sn) +{ + wake_lock_init(&sn->wlock, WAKE_LOCK_SUSPEND, "svnet"); + sn->wake_time = DEFAULT_RAW_WAKE_TIME; + sn->wake_process_time = DEFAULT_FMT_WAKE_TIME; +} + +static inline void _wake_lock_destroy(struct svnet *sn) +{ + wake_lock_destroy(&sn->wlock); +} + +static inline void _wake_lock_timeout(struct svnet *sn) +{ + wake_lock_timeout(&sn->wlock, sn->wake_time); +} + +void _non_fmt_wakelock_timeout(void) +{ + if (svnet_dev) + _wake_lock_timeout(svnet_dev); +} + +static inline void _wake_process_lock_timeout(struct svnet *sn) +{ + wake_lock_timeout(&sn->wlock, sn->wake_process_time); +} + +void _fmt_wakelock_timeout(void) +{ + if (svnet_dev) + _wake_process_lock_timeout(svnet_dev); +} + +static inline void _wake_lock_settime(struct svnet *sn, long time) +{ + if (sn) + sn->wake_time = time; +} + +static inline long _wake_lock_gettime(struct svnet *sn) +{ + return sn ? sn->wake_time : DEFAULT_RAW_WAKE_TIME; +} +#else +#define _wake_lock_init(sn) do { } while (0) +#define _wake_lock_destroy(sn) do { } while (0) +#define _wake_lock_timeout(sn) do { } while (0) +#define _wake_process_lock_timeout(sn) do { } while (0) +#define _non_fmt_wakelock_timeout() do { } while (0) +#define _fmt_wakelock_timeout() do { } while (0) +#define _wake_lock_settime(sn, time) do { } while (0) +#define _wake_lock_gettime(sn) (0) +#endif + +static unsigned long long tmp_itor; +static unsigned long long tmp_xtow; +static unsigned long long time_max_itor; +static unsigned long long time_max_xtow; +static unsigned long long time_max_read; +static unsigned long long time_max_write; + +/* +extern unsigned long long time_max_semlat; +*/ +static int _queue_evt(struct svnet_evt_head *h, u32 event); + +static ssize_t show_version(struct device *d, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "Samsung IPC version %s\n", sipc_version); +} + +static ssize_t show_waketime(struct device *d, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + unsigned int msec; + unsigned long j; + + if (!svnet_dev) + return 0; + + j = _wake_lock_gettime(svnet_dev); + msec = jiffies_to_msecs(j); + p += sprintf(p, "%u\n", msec); + + return p - buf; +} + +static ssize_t store_waketime(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long msec; + unsigned long j; + int r; + + if (!svnet_dev) + return count; + + r = strict_strtoul(buf, 10, &msec); + if (r) + return count; + + j = msecs_to_jiffies(msec); + _wake_lock_settime(svnet_dev, j); + + return count; +} + +static inline int _show_stat(char *buf) +{ + char *p = buf; + + p += sprintf(p, "Stat --------\n"); + p += sprintf(p, "\twork state: %d\n", stat.st_wq_state); + p += sprintf(p, "\trecv mailbox: %lu\n", stat.st_recv_evt); + p += sprintf(p, "\trecv phonet: %lu\n", stat.st_recv_pkt_ph); + p += sprintf(p, "\trecv packet: %lu\n", stat.st_recv_pkt_pdp); + p += sprintf(p, "\twrite count: %lu\n", stat.st_do_write); + p += sprintf(p, "\tread count: %lu\n", stat.st_do_read); + p += sprintf(p, "\trx count: %lu\n", stat.st_do_rx); + p += sprintf(p, "\n"); + + return p - buf; +} + +static ssize_t show_latency(struct device *d, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + + p += sprintf(p, "Max read latency: %12llu ns\n", time_max_itor); + p += sprintf(p, "Max read time: %12llu ns\n", time_max_read); + p += sprintf(p, "Max write latency: %12llu ns\n", time_max_xtow); + p += sprintf(p, "Max write time: %12llu ns\n", time_max_write); + /*p += sprintf(p, "Max sem. latency: %12llu ns\n", time_max_semlat);*/ + + return p - buf; +} + +static ssize_t show_debug(struct device *d, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + + if (!svnet_dev) + return 0; + + p += _show_stat(p); + + p += sprintf(p, "Event queue -----\n"); + p += sprintf(p, "\tTX queue\t%u\n", skb_queue_len(&svnet_dev->txq)); + p += sprintf(p, "\tRX queue\t%u\n", svnet_dev->rxq.len); + + p += sipc_debug_show(svnet_dev->si, p); + + return p - buf; +} + +static ssize_t store_debug(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + if (!svnet_dev) + return count; + + switch (buf[0]) { + case 'R': + sipc_debug(svnet_dev->si, buf); + break; + default: + + break; + } + + return count; +} + +static ssize_t store_whitelist(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + if (!svnet_dev) + return count; + + switch (buf[0]) { + case 0x7F: + return sipc_whitelist(svnet_dev->si, buf, count); + break; + default: + + break; + } + + return count; +} + +static DEVICE_ATTR(version, S_IRUGO, show_version, NULL); +static DEVICE_ATTR(latency, S_IRUGO, show_latency, NULL); +static DEVICE_ATTR(waketime, S_IRUGO | S_IWUSR | S_IWGRP, + show_waketime, store_waketime); +static DEVICE_ATTR(debug, S_IRUGO | S_IWUSR, show_debug, store_debug); +static DEVICE_ATTR(whitelist, S_IRUSR | S_IWUSR, NULL, store_whitelist); + +static struct attribute *svnet_attributes[] = { + &dev_attr_version.attr, + &dev_attr_waketime.attr, + &dev_attr_debug.attr, + &dev_attr_latency.attr, + &dev_attr_whitelist.attr, + NULL +}; + +static const struct attribute_group svnet_group = { + .attrs = svnet_attributes, +}; + + +int vnet_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct svnet *sn; + struct pdp_priv *priv; + + dev_dbg(&ndev->dev, "recv inet packet %p: %d bytes\n", skb, skb->len); + stat.st_recv_pkt_pdp++; + + priv = netdev_priv(ndev); + if (!priv) + goto drop; + + sn = netdev_priv(priv->parent); + if (!sn) + goto drop; + + if (!tmp_xtow) + tmp_xtow = cpu_clock(smp_processor_id()); + + skb_queue_tail(&sn->txq, skb); + + _wake_process_lock_timeout(sn); + queue_delayed_work(sn->wq, &sn->work_write, 0); + + return NETDEV_TX_OK; + +drop: + ndev->stats.tx_dropped++; + + return NETDEV_TX_OK; +} + +static int svnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct svnet *sn; + + if (skb->protocol != __constant_htons(ETH_P_PHONET)) { + dev_err(&ndev->dev, "recv not a phonet message\n"); + goto drop; + } + + stat.st_recv_pkt_ph++; + dev_dbg(&ndev->dev, "recv packet %p: %d bytes\n", skb, skb->len); + + sn = netdev_priv(ndev); + + if (sipc_check_skb(sn->si, skb)) { + sipc_do_cmd(sn->si, skb); + return NETDEV_TX_OK; + } + + if (!tmp_xtow) + tmp_xtow = cpu_clock(smp_processor_id()); + + skb_queue_tail(&sn->txq, skb); + + _wake_process_lock_timeout(sn); + queue_delayed_work(sn->wq, &sn->work_write, 0); + + return NETDEV_TX_OK; + +drop: + dev_kfree_skb(skb); + ndev->stats.tx_dropped++; + return NETDEV_TX_OK; +} + +static int _queue_evt(struct svnet_evt_head *h, u32 event) +{ + unsigned long flags; + struct svnet_evt *e; + + e = kmalloc(sizeof(struct svnet_evt), GFP_ATOMIC); + if (!e) + return -ENOMEM; + + e->event = event; + + spin_lock_irqsave(&h->lock, flags); + list_add_tail(&e->list, &h->list); + h->len++; + spin_unlock_irqrestore(&h->lock, flags); + + return 0; +} + +static void _queue_purge(struct svnet_evt_head *h) +{ + unsigned long flags; + struct svnet_evt *e, *next; + + spin_lock_irqsave(&h->lock, flags); + list_for_each_entry_safe(e, next, &h->list, list) { + list_del(&e->list); + h->len--; + kfree(e); + } + spin_unlock_irqrestore(&h->lock, flags); +} + +static u32 _dequeue_evt(struct svnet_evt_head *h) +{ + unsigned long flags; + struct list_head *p; + struct svnet_evt *e; + u32 event; + + spin_lock_irqsave(&h->lock, flags); + p = h->list.next; + if (p == &h->list) { + e = NULL; + event = 0; + } else { + e = list_entry(p, struct svnet_evt, list); + list_del(&e->list); + h->len--; + event = e->event; + } + spin_unlock_irqrestore(&h->lock, flags); + + if (e) + kfree(NULL); + + return event; +} + +static int _proc_private_event(struct svnet *sn, u32 evt) +{ + switch (evt) { + case SIPC_EXIT_MB: + dev_err(&sn->ndev->dev, "Modem crash message received\n"); + sn->exit_flag = SVNET_EXIT; + break; + case SIPC_RESET_MB: + dev_err(&sn->ndev->dev, "Modem reset message received\n"); + sn->exit_flag = SVNET_RESET; + break; + default: + return 0; + } + + /* //block for pdp_activate fail - jongmoon.suh + queue_work(sn->wq, &sn->work_exit); + */ + + return 1; +} + +static void svnet_queue_event(u32 evt, void *data) +{ + struct net_device *ndev = (struct net_device *)data; + struct svnet *sn; + int r; + + if (!tmp_itor) + tmp_itor = cpu_clock(smp_processor_id()); + + stat.st_recv_evt++; + + if (!ndev) + return; + + sn = netdev_priv(ndev); + if (!sn) + return; + + r = _proc_private_event(sn, evt); + if (r) + return; + + r = _queue_evt(&sn->rxq, evt); + if (r) { + dev_err(&sn->ndev->dev, "Not enough memory: event skipped\n"); + return; + } + + _wake_process_lock_timeout(sn); + queue_work(sn->wq, &sn->work_read); +} + +static int svnet_open(struct net_device *ndev) +{ + struct svnet *sn = netdev_priv(ndev); + + dev_dbg(&ndev->dev, "%s\n", __func__); + + /* TODO: check modem state */ + + if (!sn->si) { + sn->si = sipc_open(svnet_queue_event, ndev); + if (IS_ERR(sn->si)) { + dev_err(&ndev->dev, "IPC init error\n"); + return PTR_ERR(sn->si); + } + sn->exit_flag = SVNET_NORMAL; + } + + netif_wake_queue(ndev); + return 0; +} + +static int svnet_close(struct net_device *ndev) +{ + struct svnet *sn = netdev_priv(ndev); + + dev_dbg(&ndev->dev, "%s\n", __func__); + + if (sn->wq) + flush_workqueue(sn->wq); + skb_queue_purge(&sn->txq); + + if (sn->si) + sipc_close(&sn->si); + + netif_stop_queue(ndev); + + if (sn->wq) + flush_workqueue(sn->wq); + skb_queue_purge(&sn->txq); + + return 0; +} + +static int svnet_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) +{ + struct if_phonet_req *req = (struct if_phonet_req *)ifr; + + switch (cmd) { + case SIOCPNGAUTOCONF: + req->ifr_phonet_autoconf.device = SVNET_DEV_ADDR; + return 0; + } + + return -ENOIOCTLCMD; +} + +static const struct net_device_ops svnet_ops = { + .ndo_open = svnet_open, + .ndo_stop = svnet_close, + .ndo_start_xmit = svnet_xmit, + .ndo_do_ioctl = svnet_ioctl, +}; + +static void svnet_setup(struct net_device *ndev) +{ + ndev->features = 0; + ndev->netdev_ops = &svnet_ops; + ndev->header_ops = &phonet_header_ops; + ndev->type = ARPHRD_PHONET; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP; + ndev->mtu = PHONET_MAX_MTU; + ndev->hard_header_len = 1; + ndev->dev_addr[0] = SVNET_DEV_ADDR; + ndev->addr_len = 1; + ndev->tx_queue_len = 1000; + +/* ndev->destructor = free_netdev; */ +} + +static void svnet_read_wq(struct work_struct *work) +{ + struct svnet *sn = container_of(work, + struct svnet, work_read); + u32 event; + int r = 0; + int contd = 0; + unsigned long long t, d; + + t = cpu_clock(smp_processor_id()); + if (tmp_itor) { + d = t - tmp_itor; + _dbg(&sn->ndev->dev, "int_to_read %llu ns\n", d); + tmp_itor = 0; + if (time_max_itor < d) + time_max_itor = d; + } + + dev_dbg(&sn->ndev->dev, "%s\n", __func__); + stat.st_do_read++; + + stat.st_wq_state = 1; + event = _dequeue_evt(&sn->rxq); + while (event) { + /* isn't it possible that merge the events? */ + dev_dbg(&sn->ndev->dev, "event %x\n", event); + + if (sn->si) { + r = sipc_read(sn->si, event, &contd); + if (r < 0) { + dev_err(&sn->ndev->dev, "ret %d -> queue %x\n", + r, event); + _queue_evt(&sn->rxq, event); + break; + } + } else { + dev_err(&sn->ndev->dev, + "IPC not work, skip event %x\n", event); + } + event = _dequeue_evt(&sn->rxq); + } + + if (contd > 0) + queue_delayed_work(sn->wq, &sn->work_rx, 0); + + switch (r) { + case -EINVAL: + dev_err(&sn->ndev->dev, "Invalid argument\n"); + break; + case -EBADMSG: + dev_err(&sn->ndev->dev, "Bad message, purge the buffer\n"); + break; + case -ETIMEDOUT: + dev_err(&sn->ndev->dev, "Timed out\n"); + break; + default: + + break; + } + + stat.st_wq_state = 2; + + d = cpu_clock(smp_processor_id()) - t; + _dbg(&sn->ndev->dev, "read_time %llu ns\n", d); + if (d > time_max_read) + time_max_read = d; +} + +static void svnet_write_wq(struct work_struct *work) +{ + struct svnet *sn = container_of(work, + struct svnet, work_write.work); + int r; + unsigned long long t, d; + + t = cpu_clock(smp_processor_id()); + if (tmp_xtow) { + d = t - tmp_xtow; + _dbg(&sn->ndev->dev, "xmit_to_write %llu ns\n", d); + tmp_xtow = 0; + if (d > time_max_xtow) + time_max_xtow = d; + } + + dev_dbg(&sn->ndev->dev, "%s\n", __func__); + stat.st_do_write++; + + stat.st_wq_state = 3; + if (sn->si) + r = sipc_write(sn->si, &sn->txq); + else { + skb_queue_purge(&sn->txq); + dev_err(&sn->ndev->dev, "IPC not work, drop packet\n"); + r = 0; + } + + switch (r) { + case -ENOSPC: + dev_err(&sn->ndev->dev, "buffer is full, wait...\n"); + queue_delayed_work(sn->wq, &sn->work_write, HZ/10); + break; + case -EINVAL: + dev_err(&sn->ndev->dev, "Invalid arugment\n"); + break; + case -ENXIO: + dev_err(&sn->ndev->dev, "IPC not work, purge the queue\n"); + break; + case -ETIMEDOUT: + dev_err(&sn->ndev->dev, "Timed out\n"); + break; + default: + /* do nothing */ + break; + } + + stat.st_wq_state = 4; + d = cpu_clock(smp_processor_id()) - t; + _dbg(&sn->ndev->dev, "write_time %llu ns\n", d); + if (d > time_max_write) + time_max_write = d; +} + +static void svnet_rx_wq(struct work_struct *work) +{ + struct svnet *sn = container_of(work, + struct svnet, work_rx.work); + int r = 0; + + dev_dbg(&sn->ndev->dev, "%s\n", __func__); + stat.st_do_rx++; + + stat.st_wq_state = 5; + if (sn->si) + r = sipc_rx(sn->si); + + if (r > 0) + queue_delayed_work(sn->wq, &sn->work_rx, HZ/10); + + stat.st_wq_state = 6; +} + +static char *uevent_envs[SVNET_MAX] = { + "", + "MAILBOX=cp_reset", /* reset */ + "MAILBOX=cp_exit", /* exit */ +}; +static void svnet_exit_wq(struct work_struct *work) +{ + struct svnet *sn = container_of(work, + struct svnet, work_exit); + char *envs[2] = { NULL, NULL }; + + dev_dbg(&sn->ndev->dev, "%s: %d\n", __func__, sn->exit_flag); + + if (sn->exit_flag == SVNET_NORMAL || sn->exit_flag >= SVNET_MAX) + return; + + envs[0] = uevent_envs[sn->exit_flag]; + kobject_uevent_env(&sn->ndev->dev.kobj, KOBJ_OFFLINE, envs); + + _queue_purge(&sn->rxq); + skb_queue_purge(&sn->txq); + + if (sn->exit_flag == SVNET_EXIT) + sipc_ramdump(sn->si); + +#if 0 + rtnl_lock(); + if (netif_running(sn->ndev)) + dev_close(sn->ndev); + rtnl_unlock(); +#endif +} + +static inline void _init_data(struct svnet *sn) +{ + INIT_WORK(&sn->work_read, svnet_read_wq); + INIT_DELAYED_WORK(&sn->work_write, svnet_write_wq); + INIT_DELAYED_WORK(&sn->work_rx, svnet_rx_wq); + INIT_WORK(&sn->work_exit, svnet_exit_wq); + + INIT_LIST_HEAD(&sn->rxq.list); + spin_lock_init(&sn->rxq.lock); + sn->rxq.len = 0; + skb_queue_head_init(&sn->txq); +} + +static void _free(struct svnet *sn) +{ + if (!sn) + return; + + _wake_lock_destroy(sn); + + if (sn->group) + sysfs_remove_group(&sn->ndev->dev.kobj, &svnet_group); + + if (sn->wq) { + flush_workqueue(sn->wq); + destroy_workqueue(sn->wq); + } + + if (sn->si) { + sipc_close(&sn->si); + sipc_exit(); + } + + if (sn->ndev) + unregister_netdev(sn->ndev); + + /* sn is ndev's priv */ + free_netdev(sn->ndev); +} + +static int __init svnet_init(void) +{ + int r; + struct svnet *sn = NULL; + struct net_device *ndev; + + printk(KERN_ERR "[%s]\n", __func__); + ndev = alloc_netdev(sizeof(struct svnet), "svnet%d", svnet_setup); + if (!ndev) { + r = -ENOMEM; + goto err; + } + netif_stop_queue(ndev); + sn = netdev_priv(ndev); + + _wake_lock_init(sn); + + r = register_netdev(ndev); + if (r) { + dev_err(&ndev->dev, "failed to register netdev\n"); + goto err; + } + sn->ndev = ndev; + + _init_data(sn); + + sn->wq = create_workqueue("svnetd"); + if (!sn->wq) { + dev_err(&ndev->dev, "failed to create a workqueue\n"); + goto err; + } + + r = sysfs_create_group(&sn->ndev->dev.kobj, &svnet_group); + if (r) { + dev_err(&ndev->dev, "failed to create sysfs group\n"); + goto err; + } + sn->group = &svnet_group; + + dev_dbg(&ndev->dev, "Svnet dev: %p\n", sn); + svnet_dev = sn; + + return 0; + +err: + _free(sn); + return r; +} + +static void __exit svnet_exit(void) +{ + + _free(svnet_dev); + svnet_dev = NULL; +} + +module_init(svnet_init); +module_exit(svnet_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Suchang Woo <suchang.woo@samsung.com>"); +MODULE_DESCRIPTION("Samsung Virtual network interface"); diff --git a/drivers/phone_svn/svnet/main.h b/drivers/phone_svn/svnet/main.h new file mode 100644 index 0000000..74685e9 --- /dev/null +++ b/drivers/phone_svn/svnet/main.h @@ -0,0 +1,5 @@ +/*add interface for sipc files*/ + +void _non_fmt_wakelock_timeout(void); + +void _fmt_wakelock_timeout(void); diff --git a/drivers/phone_svn/svnet/pdp.c b/drivers/phone_svn/svnet/pdp.c new file mode 100644 index 0000000..c5a06dc --- /dev/null +++ b/drivers/phone_svn/svnet/pdp.c @@ -0,0 +1,100 @@ +/** + * Samsung Virtual Network driver using OneDram device + * + * Copyright (C) 2010 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* #define DEBUG */ + +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <linux/if.h> +#include <linux/if_arp.h> + +#include "pdp.h" + +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 struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, +/* .ndo_tx_timeout = vnet_tx_timeout, + .ndo_start_xmit = vnet_start_xmit,*/ +}; + +static void vnet_setup(struct net_device *ndev) +{ + vnet_ops.ndo_start_xmit = vnet_start_xmit; + + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->hard_header_len = 0; + ndev->addr_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +struct net_device *create_pdp(int channel, struct net_device *parent) +{ + int r; + struct pdp_priv *priv; + struct net_device *ndev; + char devname[IFNAMSIZ]; + + if (!parent) + return ERR_PTR(-EINVAL); + + sprintf(devname, "pdp%d", channel - 1); + ndev = alloc_netdev(sizeof(struct pdp_priv), devname, vnet_setup); + if (!ndev) + return ERR_PTR(-ENOMEM); + + priv = netdev_priv(ndev); + priv->channel = channel; + priv->parent = parent; + + r = register_netdev(ndev); + if (r) { + free_netdev(ndev); + return ERR_PTR(r); + } + + return ndev; +} + +void destroy_pdp(struct net_device **ndev) +{ + if (!ndev || !*ndev) + return; + + unregister_netdev(*ndev); + free_netdev(*ndev); + *ndev = NULL; +} diff --git a/drivers/phone_svn/svnet/pdp.h b/drivers/phone_svn/svnet/pdp.h new file mode 100644 index 0000000..804b0f3 --- /dev/null +++ b/drivers/phone_svn/svnet/pdp.h @@ -0,0 +1,35 @@ +/** + * SAMSUNG MODEM IPC header + * + * Copyright (C) 2010 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __PACKET_DATA_PROTOCOL_H__ +#define __PACKET_DATA_PROTOCOL_H__ + +#include <linux/netdevice.h> + +struct pdp_priv { + int channel; + struct net_device *parent; +}; + +extern struct net_device *create_pdp(int channel, struct net_device *parent); +extern void destroy_pdp(struct net_device **); +extern int vnet_start_xmit(struct sk_buff *skb, struct net_device *ndev); + +#endif /* __PACKET_DATA_PROTOCOL_H__ */ diff --git a/drivers/phone_svn/svnet/sipc.h b/drivers/phone_svn/svnet/sipc.h new file mode 100644 index 0000000..af9400b --- /dev/null +++ b/drivers/phone_svn/svnet/sipc.h @@ -0,0 +1,64 @@ +/** + * SAMSUNG MODEM IPC header + * + * Copyright (C) 2010 Samsung Electronics. All rights reserved. +* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SAMSUNG_IPC_H__ +#define __SAMSUNG_IPC_H__ + +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> + +extern const char *sipc_version; + +/* +#if 1 +# include "sipc4.h" +#else +# error "Unknown version" +#endif +*/ + +#define SIPC_RESET_MB 0xFFFFFF7E /* -2 & ~(INT_VALID) */ +#define SIPC_EXIT_MB 0xFFFFFF7F /* -1 & ~(INT_VALID) */ + +struct sipc; + +extern struct sipc *sipc_open(void (*queue)(u32 mailbox, void *data), + struct net_device *ndev); +extern void sipc_close(struct sipc **); + +extern void sipc_exit(void); + +extern int sipc_write(struct sipc *, struct sk_buff_head *); +extern int sipc_read(struct sipc *, u32 mailbox, int *cond); +extern int sipc_rx(struct sipc *); + + +/* TODO: use PN_CMD ?? */ +extern int sipc_check_skb(struct sipc *, struct sk_buff *skb); +extern int sipc_do_cmd(struct sipc *, struct sk_buff *skb); + +extern ssize_t sipc_debug_show(struct sipc *, char *); +extern int sipc_debug(struct sipc *, const char *); +extern int sipc_whitelist(struct sipc *si, const char *buf, size_t count); + +extern void sipc_ramdump(struct sipc *); + +#endif /* __SAMSUNG_IPC_H__ */ diff --git a/drivers/phone_svn/svnet/sipc4.c b/drivers/phone_svn/svnet/sipc4.c new file mode 100644 index 0000000..55aecf6 --- /dev/null +++ b/drivers/phone_svn/svnet/sipc4.c @@ -0,0 +1,2119 @@ +/** + * SAMSUNG MODEM IPC version 4 + * + * Copyright (C) 2010 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* #define DEBUG */ + +#if defined(DEBUG) +#define NOISY_DEBUG +#endif + +#include "pdp.h" +#include "sipc.h" +#include "sipc4.h" +#include "main.h" +#include <linux/vmalloc.h> +#include <linux/circ_buf.h> +#include <linux/workqueue.h> +#include <linux/errno.h> + +#include <net/sock.h> +#include <linux/if_ether.h> +#include <linux/phonet.h> +#include <net/phonet/phonet.h> + +#if defined(CONFIG_PHONE_IPC_SPI) +#include <linux/phone_svn/ipc_spi.h> +#else +#if defined(CONFIG_PHONE_IPC_HSI) +#include <linux/phone_svn/ipc_hsi.h> +#endif +#endif + +#if defined(CONFIG_KERNEL_DEBUG_SEC) +#include <linux/kernel_sec_common.h> +#define ERRMSG "Unknown CP Crash" +static char cp_errmsg[65]; +static void _go_dump(struct sipc *si); +#else +#define _go_dump(si) do { } while (0) +#endif + +#if defined(NOISY_DEBUG) +static struct device *_dev; +# define _dbg(format, arg...) \ + dev_dbg(_dev, format, ## arg) +#else +# define _dbg(format, arg...) \ + do { } while (0) +#endif + +const char *sipc_version = "4.1"; + +static const char hdlc_start[1] = { HDLC_START }; +static const char hdlc_end[1] = { HDLC_END }; + +struct mailbox_data { + u16 mask_send; + u16 mask_req_ack; + u16 mask_res_ack; +}; + +static struct mailbox_data mb_data[IPCIDX_MAX] = { + { + .mask_send = MBD_SEND_FMT, + .mask_req_ack = MBD_REQ_ACK_FMT, + .mask_res_ack = MBD_RES_ACK_FMT, + }, + { + .mask_send = MBD_SEND_RAW, + .mask_req_ack = MBD_REQ_ACK_RAW, + .mask_res_ack = MBD_RES_ACK_RAW, + }, + { + .mask_send = MBD_SEND_RFS, + .mask_req_ack = MBD_REQ_ACK_RFS, + .mask_res_ack = MBD_RES_ACK_RFS, + }, +}; + +/* semaphore latency */ +unsigned long long time_max_semlat; + +struct sipc; +struct ringbuf; + +struct ringbuf_info { + unsigned int out_off; + unsigned int in_off; + unsigned int size; + int (*read)(struct sipc *si, int inbuf, struct ringbuf *rb); +}; + +struct ringbuf { + unsigned char *out_base; + unsigned char *in_base; + struct ringbuf_cont *cont; + struct ringbuf_info *info; +}; +/* +#define rb_size info->size +#define rb_read info->read +#define rb_out_head cont->out_head +#define rb_out_tail cont->out_tail +#define rb_in_head cont->in_head +#define rb_in_tail cont->in_tail +*/ + +static int _read_fmt(struct sipc *si, int inbuf, struct ringbuf *rb); +static int _read_raw(struct sipc *si, int inbuf, struct ringbuf *rb); +static int _read_rfs(struct sipc *si, int inbuf, struct ringbuf *rb); + +static struct ringbuf_info rb_info[IPCIDX_MAX] = { + { + .out_off = FMT_OUT, + .in_off = FMT_IN, + .size = FMT_SZ, + .read = _read_fmt, + }, + { + .out_off = RAW_OUT, + .in_off = RAW_IN, + .size = RAW_SZ, + .read = _read_raw, + }, + { + .out_off = RFS_OUT, + .in_off = RFS_IN, + .size = RFS_SZ, + .read = _read_rfs, + }, +}; + +#define FRAG_BLOCK_MAX (PAGE_SIZE - sizeof(struct list_head) \ + - sizeof(u32) - sizeof(char *)) +struct frag_block { + struct list_head list; + u32 len; + char *ptr; + char buf[FRAG_BLOCK_MAX]; +}; + +struct frag_list { + struct list_head list; + u8 msg_id; + u32 len; + + struct list_head block_head; +}; + +struct frag_head { + struct list_head head; + unsigned long bitmap[FMT_ID_SIZE/BITS_PER_LONG]; +}; + +struct frag_info { + struct sk_buff *skb; + unsigned int offset; + u8 msg_id; +}; + +struct sipc { + struct sipc_mapped *map; + struct ringbuf rb[IPCIDX_MAX]; + + struct resource *res; + + void (*queue)(u32, void *); + void *queue_data; + + /* for fragmentation */ + u8 msg_id; + char *frag_buf; + struct frag_info frag; + + /* for merging */ + struct frag_head frag_map; + + int od_rel; /* onedram authority release */ + + struct net_device *svndev; + + const struct attribute_group *group; + + struct sk_buff_head rfs_rx; +}; + +/* sizeof(struct phonethdr) + NET_SKB_PAD > SMP_CACHE_BYTES */ + +/* SMP_CACHE_BYTES > sizeof(struct phonethdr) + NET_SKB_PAD */ +#define RFS_MTU (PAGE_SIZE - SMP_CACHE_BYTES) +#define RFS_TX_RATE 4 + +/* set at storage device */ +unsigned int factory_test_force_sleep; +EXPORT_SYMBOL(factory_test_force_sleep); + +/* TODO: move PDP related codes to other source file */ +static DEFINE_MUTEX(pdp_mutex); +static struct net_device *pdp_devs[PDP_MAX]; +static int pdp_cnt; +unsigned long pdp_bitmap[PDP_MAX/BITS_PER_LONG]; + +static void clear_pdp_wq(struct work_struct *work); +static DECLARE_WORK(pdp_work, clear_pdp_wq); + +static ssize_t show_act(struct device *d, + struct device_attribute *attr, char *buf); +static ssize_t show_deact(struct device *d, + struct device_attribute *attr, char *buf); +static ssize_t store_act(struct device *d, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_deact(struct device *d, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t show_suspend(struct device *d, + struct device_attribute *attr, char *buf); +static ssize_t store_suspend(struct device *d, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t store_resume(struct device *d, + struct device_attribute *attr, const char *buf, size_t count); + +static DEVICE_ATTR(activate, S_IRUGO | S_IWUSR | S_IWGRP, + show_act, store_act); +static DEVICE_ATTR(deactivate, S_IRUGO | S_IWUSR | S_IWGRP, + show_deact, store_deact); +static DEVICE_ATTR(suspend, S_IRUGO | S_IWUSR | S_IWGRP, + show_suspend, store_suspend); +static DEVICE_ATTR(resume, S_IRUGO | S_IWUSR | S_IWGRP, NULL, + store_resume); + +static struct attribute *pdp_attributes[] = { + &dev_attr_activate.attr, + &dev_attr_deactivate.attr, + &dev_attr_suspend.attr, + &dev_attr_resume.attr, + NULL +}; + +static const struct attribute_group pdp_group = { + .name = "pdp", + .attrs = pdp_attributes, +}; + + +#if defined(NOISY_DEBUG) +#define DUMP_LIMIT 32 +static char dump_buf[64]; +void _dbg_dump(u8 *buf, int size) +{ + int i; + int len = 0; + + if (!buf) + return; + + if (size > DUMP_LIMIT) + size = DUMP_LIMIT; + + for (i = 0; i < 32 && i < size; i++) { + len += sprintf(&dump_buf[len], "%02x ", buf[i]); + if ((i & 0xf) == 0xf) { + dump_buf[len] = '\0'; + _dbg("dump %04x [ %s]\n", (i>>4), dump_buf); + len = 0; + } + } + if (len) { + dump_buf[len] = '\0'; + _dbg("dump %04x [ %s]\n", i, dump_buf); + } +} +#else +# define _dbg_dump(buf, size) do { } while (0) +#endif + +static int cp_state = 1; +void modem_state_changed(int state) +{ + printk(KERN_ERR "cp state change. state : %d\n", state); + + cp_state = state; +} +EXPORT_SYMBOL(modem_state_changed); + +static int _get_auth(void) +{ + int r; + unsigned long long t, d; + + t = cpu_clock(smp_processor_id()); + + r = onedram_get_auth(MB_CMD(MBC_REQ_SEM)); + + d = cpu_clock(smp_processor_id()) - t; + if (d > time_max_semlat) + time_max_semlat = d; + + return r; +} + +static void _put_auth(struct sipc *si) +{ + if (!si) + return; + + onedram_put_auth(0); + + if (si->od_rel && !onedram_rel_sem()) { + onedram_write_mailbox(MB_CMD(MBC_RES_SEM)); + si->od_rel = 0; + } +} + +static inline void _req_rel_auth(struct sipc *si) +{ + si->od_rel = 1; +} + +static int _get_auth_try(void) +{ + return onedram_get_auth(0); +} + +static void _check_buffer(struct sipc *si) +{ + int i; + u32 mailbox; + +#if 0 + i = onedram_read_sem(); + if (i != 0x1) + return; +#endif + i = _get_auth_try(); + if (i) + return; + + mailbox = 0; + + for (i = 0 ; i < IPCIDX_MAX; i++) { + int inbuf; + struct ringbuf *rb; + + rb = &si->rb[i]; + inbuf = CIRC_CNT(rb->cont->in_head, + rb->cont->in_tail, rb->info->size); + if (!inbuf) + continue; + + mailbox |= mb_data[i].mask_send; + } + _put_auth(si); + + if (mailbox) + si->queue(MB_DATA(mailbox), si->queue_data); +} + +static void _do_command(struct sipc *si, u32 mailbox) +{ + int r; + u32 cmd = (mailbox & MBC_MASK) & ~(MB_CMD(0)); + + switch (cmd) { + case MBC_REQ_SEM: + r = onedram_rel_sem(); + if (r) { + dev_dbg(&si->svndev->dev, "onedram in use, " + "defer releasing semaphore\n"); + _req_rel_auth(si); + } else + onedram_write_mailbox(MB_CMD(MBC_RES_SEM)); + break; + case MBC_RES_SEM: + /* do nothing */ + break; + case MBC_PHONE_START: + onedram_write_mailbox(MB_CMD(MBC_INIT_END) | CP_BOOT_AIRPLANE + | AP_OS_ANDROID); + break; + case MBC_RESET: + printk(KERN_ERR "svnet reset mailbox msg : 0x%08x\n", mailbox); + si->queue(SIPC_RESET_MB, si->queue_data); + break; + case MBC_ERR_DISPLAY: + printk(KERN_ERR "svnet error display mailbox msg : 0x%08x\n", + mailbox); + si->queue(SIPC_EXIT_MB, si->queue_data); + break; + /* TODO : impletment other commands... */ + default: + /* do nothing */ + + break; + } +} + +void sipc_handler(u32 mailbox, void *data) +{ + struct sipc *si = (struct sipc *)data; + + if (!si || !si->queue) + return; + + dev_dbg(&si->svndev->dev, "recv mailbox %x\n", mailbox); + +#if defined(CONFIG_KERNEL_DEBUG_SEC) + if (mailbox == KERNEL_SEC_DUMP_AP_DEAD_ACK) + kernel_sec_set_cp_ack(); +#endif + + if ((mailbox & MB_VALID) == 0) { + dev_err(&si->svndev->dev, "Invalid mailbox message: %x\n", + mailbox); + return; + } + + if (mailbox & MB_COMMAND) { + _check_buffer(si); + _do_command(si, mailbox); + return; + } + + si->queue(mailbox, si->queue_data); +} + +static inline void _init_data(struct sipc *si, unsigned char *base) +{ + int i; + + si->map = (struct sipc_mapped *)base; + si->map->magic = 0x0; + si->map->access = 0x0; + + for (i = 0; i < IPCIDX_MAX; i++) { + struct ringbuf *r = &si->rb[i]; + struct ringbuf_info *info = &rb_info[i]; + struct ringbuf_cont *cont = &si->map->rbcont[i]; + + r->out_base = base + info->out_off; + r->in_base = base + info->in_off; + r->info = info; + r->cont = cont; + + cont->out_head = 0; + cont->out_tail = 0; + cont->in_head = 0; + cont->in_tail = 0; + } +} + +static void _init_proc(struct sipc *si) +{ + u32 mailbox; + int r; + + dev_dbg(&si->svndev->dev, "%s\n", __func__); + r = onedram_read_mailbox(&mailbox); + if (r) + return; + + mailbox = 0xc8; + sipc_handler(mailbox, si); +} + +struct sipc *sipc_open(void (*queue)(u32, void*), struct net_device *ndev) +{ + struct sipc *si; + struct resource *res; + int r; + void *onedram_vbase; + + if (!queue || !ndev) + return ERR_PTR(-EINVAL); + + si = kzalloc(sizeof(struct sipc), GFP_KERNEL); + if (!si) + return ERR_PTR(-ENOMEM); + + /* If FMT_SZ grown up, MUST be changed!! */ + si->frag_buf = vmalloc(FMT_SZ); + memset(si->frag_buf, 0x00, FMT_SZ); + if (!si->frag_buf) { + sipc_close(&si); + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&si->frag_map.head); + + res = onedram_request_region(0, SIPC_MAP_SIZE, SIPC_NAME); + if (!res) { + sipc_close(&si); + return ERR_PTR(-EBUSY); + } + si->res = res; + + r = onedram_register_handler(sipc_handler, si); + if (r) { + sipc_close(&si); + return ERR_PTR(r); + } + si->queue = queue; + si->queue_data = ndev; + si->svndev = ndev; + + /* TODO: need?? */ + if (work_pending(&pdp_work)) + flush_work(&pdp_work); + + r = sysfs_create_group(&si->svndev->dev.kobj, &pdp_group); + if (r) { + sipc_close(&si); + return ERR_PTR(r); + } + si->group = &pdp_group; + +#if defined(NOISY_DEBUG) + _dev = &si->svndev->dev; +#endif + + onedram_get_vbase(&onedram_vbase); + + if (onedram_vbase) + _init_data(si, (unsigned char *)onedram_vbase); + else + _init_data(si, (unsigned char *)res->start); + + skb_queue_head_init(&si->rfs_rx); + + /* process init message */ + _init_proc(si); + + dev_dbg(&si->svndev->dev, "sipc_open Done.\n"); + + return si; +} + +static void clear_pdp_wq(struct work_struct *work) +{ + int i; + + mutex_lock(&pdp_mutex); + + for (i = 0; i < sizeof(pdp_devs)/sizeof(pdp_devs[0]); i++) { + if (pdp_devs[i]) { + destroy_pdp(&pdp_devs[i]); + clear_bit(i, pdp_bitmap); + } + } + pdp_cnt = 0; + + mutex_unlock(&pdp_mutex); +} + +void sipc_exit(void) +{ + if (work_pending(&pdp_work)) + flush_work(&pdp_work); + else + clear_pdp_wq(NULL); +} + +void sipc_close(struct sipc **psi) +{ + struct sipc *si; + + if (!psi || !*psi) + return; + + si = *psi; + + if (si->group && si->svndev) { + int i; + sysfs_remove_group(&si->svndev->dev.kobj, si->group); + + mutex_lock(&pdp_mutex); + for (i = 0; i < sizeof(pdp_devs)/sizeof(pdp_devs[0]); i++) { + if (pdp_devs[i]) + netif_stop_queue(pdp_devs[i]); + } + mutex_unlock(&pdp_mutex); + schedule_work(&pdp_work); + } + + if (si->frag_buf) + vfree(si->frag_buf); + + if (si->queue) + onedram_unregister_handler(sipc_handler); + + if (si->res) + onedram_release_region(0, SIPC_MAP_SIZE); + + kfree(si); + *psi = NULL; +} + +static inline void _wake_queue(int idx) +{ + mutex_lock(&pdp_mutex); + + if (pdp_devs[idx] && !test_bit(idx, pdp_bitmap)) + netif_wake_queue(pdp_devs[idx]); + + mutex_unlock(&pdp_mutex); +} + +static int __write(struct ringbuf *rb, u8 *buf, unsigned int size) +{ + int c; + int len = 0; + + if (!cp_state) { + printk(KERN_ERR "__write : cp_state : %d\n", cp_state); + return -EPERM; + } + + _dbg("%s b: size %u head %u tail %u\n", __func__, + size, rb->cont->out_head, rb->cont->out_tail); + _dbg_dump(buf, size); + + while (1) { + c = CIRC_SPACE_TO_END(rb->cont->out_head, + rb->cont->out_tail, rb->info->size); + if (size < c) + c = size; + if (c <= 0) + break; + memcpy(rb->out_base + rb->cont->out_head, buf, c); + rb->cont->out_head = (rb->cont->out_head + c) + & (rb->info->size - 1); + buf += c; + size -= c; + len += c; + } + + _dbg("%s a: size %u head %u tail %u\n", __func__, + len, rb->cont->out_head, rb->cont->out_tail); + + return len; +} + +static inline void _set_raw_hdr(struct raw_hdr *h, int res, + unsigned int len, int control) +{ + h->len = len; + h->channel = CHID(res); + h->control = 0; +} + +static int _write_raw_buf(struct ringbuf *rb, int res, struct sk_buff *skb) +{ + int len; + struct raw_hdr h; + + _dbg("%s: packet %p res 0x%02x\n", __func__, skb, res); + + len = skb->len + sizeof(h); + + _set_raw_hdr(&h, res, len, 0); + + len = __write(rb, (u8 *)hdlc_start, sizeof(hdlc_start)); + len += __write(rb, (u8 *)&h, sizeof(h)); + len += __write(rb, skb->data, skb->len); + len += __write(rb, (u8 *)hdlc_end, sizeof(hdlc_end)); + + return len; +} + +static int _write_raw_skb(struct ringbuf *rb, int res, struct sk_buff *skb) +{ + char *b; + + if (!cp_state) { + printk(KERN_ERR "_write_raw_skb : cp_state : %d\n", cp_state); + return -EPERM; + } + + _dbg("%s: packet %p res 0x%02x\n", __func__, skb, res); + + b = skb_put(skb, sizeof(hdlc_end)); + memcpy(b, hdlc_end, sizeof(hdlc_end)); + + b = skb_push(skb, sizeof(struct raw_hdr) + sizeof(hdlc_start)); + memcpy(b, hdlc_start, sizeof(hdlc_start)); + + b += sizeof(hdlc_start); + + _set_raw_hdr((struct raw_hdr *)b, res, + skb->len - sizeof(hdlc_start) - sizeof(hdlc_end), 0); + + return __write(rb, skb->data, skb->len); +} + +static int _write_raw(struct ringbuf *rb, struct sk_buff *skb, int res) +{ + int len; + int space; + + space = CIRC_SPACE(rb->cont->out_head, + rb->cont->out_tail, rb->info->size); + if (space < skb->len + sizeof(struct raw_hdr) + + sizeof(hdlc_start) + sizeof(hdlc_end)) + return -ENOSPC; + mutex_lock(&pdp_mutex); + if (skb_headroom(skb) > (sizeof(struct raw_hdr) + sizeof(hdlc_start)) + && skb_tailroom(skb) > sizeof(hdlc_end)) { + len = _write_raw_skb(rb, res, skb); + } else { + len = _write_raw_buf(rb, res, skb); + } + mutex_unlock(&pdp_mutex); + if (res >= PN_PDP_START && res <= PN_PDP_END) + _wake_queue(PDP_ID(res)); + else + netif_wake_queue(skb->dev); + return len; +} + +static int _write_rfs_buf(struct ringbuf *rb, struct sk_buff *skb) +{ + int len; + + _dbg("%s: packet %p\n", __func__, skb); + len = __write(rb, (u8 *)hdlc_start, sizeof(hdlc_start)); + len += __write(rb, skb->data, skb->len); + len += __write(rb, (u8 *)hdlc_end, sizeof(hdlc_end)); + + return len; +} + +static int _write_rfs_skb(struct ringbuf *rb, struct sk_buff *skb) +{ + char *b; + + if (!cp_state) { + printk(KERN_ERR "_write_rfs_skb : cp_state : %d\n", cp_state); + return -EPERM; + } + + _dbg("%s: packet %p\n", __func__, skb); + b = skb_put(skb, sizeof(hdlc_end)); + memcpy(b, hdlc_end, sizeof(hdlc_end)); + + b = skb_push(skb, sizeof(hdlc_start)); + memcpy(b, hdlc_start, sizeof(hdlc_start)); + + return __write(rb, skb->data, skb->len); +} + +static int _write_rfs(struct ringbuf *rb, struct sk_buff *skb) +{ + int len; + int space; + + space = CIRC_SPACE(rb->cont->out_head, + rb->cont->out_tail, rb->info->size); + if (space < skb->len + sizeof(hdlc_start) + sizeof(hdlc_end)) + return -ENOSPC; + + if (skb_headroom(skb) > sizeof(hdlc_start) + && skb_tailroom(skb) > sizeof(hdlc_end)) { + len = _write_rfs_skb(rb, skb); + } else { + len = _write_rfs_buf(rb, skb); + } + + netif_wake_queue(skb->dev); + return len; +} + +static int _write_fmt_buf(char *frag_buf, struct ringbuf *rb, + struct sk_buff *skb, struct frag_info *fi, int wlen, + u8 control) +{ + char *buf = frag_buf; + struct fmt_hdr *h; + + if (!cp_state) { + printk(KERN_ERR "_write_fmt_buf : cp_state : %d\n", cp_state); + return -EPERM; + } + + memcpy(buf, hdlc_start, sizeof(hdlc_start)); + buf += sizeof(hdlc_start); + + h = (struct fmt_hdr *)buf; + h->len = sizeof(struct fmt_hdr) + wlen; + h->control = control; + buf += sizeof(struct fmt_hdr); + + memcpy(buf, skb->data + fi->offset, wlen); + buf += wlen; + + memcpy(buf, hdlc_end, sizeof(hdlc_end)); + buf += sizeof(hdlc_end); + + return __write(rb, frag_buf, buf - frag_buf); +} + +static int _write_fmt(struct sipc *si, struct ringbuf *rb, struct sk_buff *skb) +{ + int len; + int space; + int remain; + struct frag_info *fi = &si->frag; + + if (skb != fi->skb) { + /* new packet */ + fi->skb = skb; + fi->offset = 0; + } + + len = 0; + remain = skb->len - fi->offset; + + _dbg("%s: packet %p length %d sent %d\n", + __func__, skb, skb->len, fi->offset); + + while (remain > 0) { + int wlen; + u8 control; + + space = CIRC_SPACE(rb->cont->out_head, + rb->cont->out_tail, rb->info->size); + space -= sizeof(struct fmt_hdr) + + sizeof(hdlc_start) + sizeof(hdlc_end); + if (space < FMT_TX_MIN) + return -ENOSPC; + + if (remain > space) { + /* multiple frame */ + wlen = space; + control = 0x1 | FMT_MB_MASK; + } else { + wlen = remain; + if (fi->offset == 0) { + /* single frame */ + control = 0x0; + } else { + /* last frmae */ + control = 0x1; + } + } + + wlen = _write_fmt_buf(si->frag_buf, rb, skb, fi, wlen, control); + if (wlen < 0) + return wlen; + + len += wlen; + + wlen -= sizeof(hdlc_start) + sizeof(struct fmt_hdr) + + sizeof(hdlc_end); + + fi->offset += wlen; + remain -= wlen; + } + + if (len > 0) { + fi->skb = NULL; + fi->offset = 0; + } + + netif_wake_queue(skb->dev); + return len; /* total write bytes */ +} + +static int _write(struct sipc *si, int res, struct sk_buff *skb, u32 *mailbox) +{ + int r; + int rid; + + rid = res_to_ridx(res); + if (rid < 0 || rid >= IPCIDX_MAX) + return -EINVAL; + + switch (rid) { + case IPCIDX_FMT: + r = _write_fmt(si, &si->rb[rid], skb); + break; + case IPCIDX_RAW: + r = _write_raw(&si->rb[rid], skb, res); + break; + case IPCIDX_RFS: + r = _write_rfs(&si->rb[rid], skb); + break; + default: + /* do nothing */ + r = 0; + break; + } + + if (r > 0) + *mailbox |= mb_data[rid].mask_send; + + _dbg("%s: return %d\n", __func__, r); + return r; +} + +static inline void _update_stat(struct net_device *ndev, unsigned int len) +{ + if (!ndev) + return; + + ndev->stats.tx_bytes += len; + ndev->stats.tx_packets++; +} + +static inline int _write_pn(struct sipc *si, struct sk_buff *skb, u32 *mb) +{ + int r; + struct phonethdr *ph; + + ph = pn_hdr(skb); + skb_pull(skb, sizeof(struct phonethdr) + 1); + + r = _write(si, ph->pn_res, skb, mb); + if (r < 0) + skb_push(skb, sizeof(struct phonethdr) + 1); + + return r; +} + +int sipc_write(struct sipc *si, struct sk_buff_head *sbh) +{ + int r; + u32 mailbox; + struct sk_buff *skb; + + if (!sbh) + return -EINVAL; + + if (!si) { + skb_queue_purge(sbh); + return -ENXIO; + } + + r = _get_auth(); + if (r) { + if (factory_test_force_sleep) { + printk(KERN_ERR "tx ignored for factory force sleep\n"); + skb_queue_purge(sbh); + return 0; + } else { + return r; + } + } + + r = mailbox = 0; + skb = skb_dequeue(sbh); + while (skb) { + struct net_device *ndev = skb->dev; + int len = skb->len; + + dev_dbg(&si->svndev->dev, "write packet %p\n", skb); + + if (skb->protocol != __constant_htons(ETH_P_PHONET)) { + struct pdp_priv *priv; + priv = netdev_priv(ndev); + r = _write(si, PN_PDP(priv->channel), skb, &mailbox); + } else + r = _write_pn(si, skb, &mailbox); + + if (r < 0) + break; + + _update_stat(ndev, len); + dev_kfree_skb_any(skb); + + skb = skb_dequeue(sbh); + } + + _req_rel_auth(si); + _put_auth(si); + + if (mailbox) + onedram_write_mailbox(MB_DATA(mailbox)); + + if (r < 0) { + if (r == -ENOSPC) { + dev_err(&si->svndev->dev, + "write nospc queue %p\n", skb); + skb_queue_head(sbh, skb); + netif_stop_queue(skb->dev); + } else { + dev_err(&si->svndev->dev, + "write err %d, drop %p\n", r, skb); + dev_kfree_skb_any(skb); + } + } + + return r; +} + +extern int __read(struct ringbuf *rb, unsigned char *buf, unsigned int size) +{ + int c; + int len = 0; + unsigned char *p = buf; + + if (!cp_state) { + printk(KERN_ERR "__read : cp_state : %d\n", cp_state); + return -EPERM; + } + + _dbg("%s b: size %u head %u tail %u\n", __func__, + size, rb->cont->in_head, rb->cont->in_tail); + + while (1) { + c = CIRC_CNT_TO_END(rb->cont->in_head, + rb->cont->in_tail, rb->info->size); + if (size < c) + c = size; + if (c <= 0) + break; + if (p) { + memcpy(p, rb->in_base + rb->cont->in_tail, c); + p += c; + } + rb->cont->in_tail = (rb->cont->in_tail + c) + & (rb->info->size - 1); + size -= c; + len += c; + } + + _dbg("%s a: size %u head %u tail %u\n", __func__, + len, rb->cont->in_head, rb->cont->in_tail); + _dbg_dump(buf, len); + + return len; +} + +static inline void _get_raw_hdr(struct raw_hdr *h, int *res, + unsigned int *len, int *control) +{ + if (res) + *res = PN_RAW(h->channel); + if (len) + *len = h->len; + if (control) + *control = h->control; +} + +static inline void _phonet_rx(struct net_device *ndev, + struct sk_buff *skb, int res) +{ + int r; + struct phonethdr *ph; + + skb->protocol = __constant_htons(ETH_P_PHONET); + + ph = (struct phonethdr *)skb_push(skb, sizeof(struct phonethdr)); + ph->pn_rdev = ndev->dev_addr[0]; + ph->pn_sdev = 0; + ph->pn_res = res; + ph->pn_length = __cpu_to_be16(skb->len + 2 - sizeof(*ph)); + ph->pn_robj = 0; + ph->pn_sobj = 0; + + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + skb_reset_mac_header(skb); + + r = netif_rx_ni(skb); + if (r != NET_RX_SUCCESS) + dev_err(&ndev->dev, "phonet rx error: %d\n", r); + + _dbg("%s: res 0x%02x packet %p len %d\n", __func__, res, skb, skb->len); +} + +static int _read_pn(struct net_device *ndev, struct ringbuf *rb, int len, + int res) +{ + int r; + struct sk_buff *skb; + char *p; + int read_len = len + sizeof(hdlc_end); + + _dbg("%s: res 0x%02x data %d\n", __func__, res, len); + + skb = netdev_alloc_skb(ndev, read_len + sizeof(struct phonethdr)); + if (unlikely(!skb)) + return -ENOMEM; + + skb_reserve(skb, sizeof(struct phonethdr)); + + p = skb_put(skb, len); + r = __read(rb, p, read_len); + if (r != read_len) { + kfree_skb(skb); + return -EBADMSG; + } + + _phonet_rx(ndev, skb, res); + + return r; +} + +static inline struct sk_buff *_alloc_phskb(struct net_device *ndev, int len) +{ + struct sk_buff *skb; + + skb = netdev_alloc_skb(ndev, len + sizeof(struct phonethdr)); + if (likely(skb)) + skb_reserve(skb, sizeof(struct phonethdr)); + + return skb; +} + +static inline int _alloc_rfs(struct net_device *ndev, + struct sk_buff_head *list, int len) +{ + int r = 0; + struct sk_buff *skb; + + __skb_queue_head_init(list); + + while (len > 0) { + skb = _alloc_phskb(ndev, RFS_MTU); + if (unlikely(!skb)) { + r = -ENOMEM; + break; + } + __skb_queue_tail(list, skb); + len -= RFS_MTU; + } + + return r; +} +static void _free_rfs(struct sk_buff_head *list) +{ + struct sk_buff *skb; + + skb = __skb_dequeue(list); + while (skb) { + __kfree_skb(skb); + skb = __skb_dequeue(list); + } +} + +static inline int _read_rfs_rb(struct ringbuf *rb, int len, + struct sk_buff_head *list) +{ + int r; + int read_len; + struct sk_buff *skb; + char *p; + + read_len = 0; + skb = list->next; + while (skb != (struct sk_buff *)list) { + int rd = RFS_MTU; + + if (skb == list->next) /* first sk has header */ + rd -= sizeof(struct rfs_hdr); + + if (len < rd) + rd = len; + + p = skb_put(skb, rd); + r = __read(rb, p, rd); + if (r != rd) + return -EBADMSG; + + len -= r; + read_len += r; + skb = skb->next; + } + + return read_len; +} + +static int _read_rfs_data(struct sipc *si, struct ringbuf *rb, int len, + struct rfs_hdr *h) +{ + int r; + struct sk_buff_head list; + struct sk_buff *skb; + char *p; + int read_len; + struct net_device *ndev = si->svndev; + + if (!cp_state) { + printk(KERN_ERR "_read_rfs_data : cp_state : %d\n", cp_state); + return -EPERM; + } + + _dbg("%s: %d bytes\n", __func__, len); + + /* alloc sk_buffs */ + r = _alloc_rfs(ndev, &list, len + sizeof(struct rfs_hdr)); + if (r) + goto free_skb; + + skb = list.next; + p = skb_put(skb, sizeof(struct rfs_hdr)); + memcpy(p, h, sizeof(struct rfs_hdr)); + + /* read data all */ + r = _read_rfs_rb(rb, len, &list); + if (r < 0) + goto free_skb; + + read_len = r; + + /* move to rfs_rx queue */ + skb = __skb_dequeue(&list); + while (skb) { + skb_queue_tail(&si->rfs_rx, skb); + skb = __skb_dequeue(&list); + } + + /* remove hdlc_end */ + read_len += __read(rb, NULL, sizeof(hdlc_end)); + + return read_len; + +free_skb: + _free_rfs(&list); + return r; +} + +static int _read_pdp(struct ringbuf *rb, int len, + int res) +{ + int r; + struct sk_buff *skb; + char *p; + int read_len = len + sizeof(hdlc_end); + struct net_device *ndev; + + _dbg("%s: res 0x%02x data %d\n", __func__, res, len); + + mutex_lock(&pdp_mutex); + + ndev = pdp_devs[PDP_ID(res)]; + if (!ndev) { + /* drop data */ + r = __read(rb, NULL, read_len); + mutex_unlock(&pdp_mutex); + return r; + } + + skb = netdev_alloc_skb(ndev, read_len); + if (unlikely(!skb)) { + mutex_unlock(&pdp_mutex); + return -ENOMEM; + } + + p = skb_put(skb, len); + r = __read(rb, p, read_len); + if (r != read_len) { + mutex_unlock(&pdp_mutex); + kfree_skb(skb); + return -EBADMSG; + } + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + mutex_unlock(&pdp_mutex); + + read_len = r; + + skb->protocol = __constant_htons(ETH_P_IP); + + skb_reset_mac_header(skb); + + _dbg("%s: pdp packet %p len %d\n", __func__, skb, skb->len); + + r = netif_rx_ni(skb); + if (r != NET_RX_SUCCESS) + dev_err(&ndev->dev, "pdp rx error: %d\n", r); + + return read_len; +} + +static int _read_raw(struct sipc *si, int inbuf, struct ringbuf *rb) +{ + int r; + char buf[sizeof(struct raw_hdr) + sizeof(hdlc_start)]; + int res, data_len; + u32 tail; + + while (inbuf > 0) { + tail = rb->cont->in_tail; + + r = __read(rb, buf, sizeof(buf)); + if (r < sizeof(buf) || + strncmp(buf, hdlc_start, sizeof(hdlc_start))) { + dev_err(&si->svndev->dev, "Bad message: %c %d\n", + buf[0], r); + return -EBADMSG; + } + inbuf -= r; + + _get_raw_hdr((struct raw_hdr *)&buf[sizeof(hdlc_start)], + &res, &data_len, NULL); + + data_len -= sizeof(struct raw_hdr); + + if (res >= PN_PDP_START && res <= PN_PDP_END) + r = _read_pdp(rb, data_len, res); + else + r = _read_pn(si->svndev, rb, data_len, res); + + if (r < 0) { + if (r == -ENOMEM) + rb->cont->in_tail = tail; + + return r; + } + + inbuf -= r; + } + + return 0; +} + +static int _read_rfs(struct sipc *si, int inbuf, struct ringbuf *rb) +{ + int r; + char buf[sizeof(struct rfs_hdr) + sizeof(hdlc_start)]; + int data_len; + u32 tail; + struct rfs_hdr *h; + + h = (struct rfs_hdr *)&buf[sizeof(hdlc_start)]; + while (inbuf > 0) { + tail = rb->cont->in_tail; + + r = __read(rb, buf, sizeof(buf)); + if (r < sizeof(buf) || + strncmp(buf, hdlc_start, sizeof(hdlc_start))) { + dev_err(&si->svndev->dev, "Bad message: %c %d\n", + buf[0], r); + return -EBADMSG; + } + inbuf -= r; + + data_len = h->len - sizeof(struct rfs_hdr); + + r = _read_rfs_data(si, rb, data_len, h); + if (r < 0) { + if (r == -ENOMEM) + rb->cont->in_tail = tail; + + return r; + } + + inbuf -= r; + } + + return 0; +} + + +static struct frag_list *_find_frag_list(u8 control, struct frag_head *fh) +{ + struct frag_list *fl; + u8 msg_id = control & FMT_ID_MASK; + + if (!test_bit(msg_id, fh->bitmap)) + return NULL; + + list_for_each_entry(fl, &fh->head, list) { + if (fl->msg_id == msg_id) + break; + } + + return fl; +} + +static int _fill_skb(struct sk_buff *skb, struct frag_list *fl) +{ + struct frag_block *fb, *n; + int offset = 0; + char *p; + + if (!cp_state) { + printk(KERN_ERR "_fill_skb : cp_state : %d\n", cp_state); + return -EPERM; + } + + list_for_each_entry_safe(fb, n, &fl->block_head, list) { + p = skb_put(skb, fb->len); + memcpy(p, fb->buf, fb->len); + offset += fb->len; + list_del(&fb->list); + kfree(fb); + } + + return offset; +} + +static void _destroy_frag_list(struct frag_list *fl, struct frag_head *fh) +{ + struct frag_block *fb, *n; + + if (!fl || !fh) + return; + + list_for_each_entry_safe(fb, n, &fl->block_head, list) { + kfree(fb); + } + + clear_bit(fl->msg_id, fh->bitmap); + list_del(&fl->list); + kfree(fl); +} + +static struct frag_list *_create_frag_list(u8 control, struct frag_head *fh) +{ + struct frag_list *fl; + u8 msg_id = control & FMT_ID_MASK; + + if (test_bit(msg_id, fh->bitmap)) { + fl = _find_frag_list(control, fh); + _destroy_frag_list(fl, fh); + } + + fl = kmalloc(sizeof(struct frag_list), GFP_KERNEL); + if (!fl) + return NULL; + + INIT_LIST_HEAD(&fl->block_head); + fl->msg_id = msg_id; + fl->len = 0; + list_add(&fl->list, &fh->head); + set_bit(msg_id, fh->bitmap); + + return fl; +} + +static inline struct frag_block *_create_frag_block(struct frag_list *fl) +{ + struct frag_block *fb; + + fb = kmalloc(sizeof(struct frag_block), GFP_KERNEL); + if (!fb) + return NULL; + + fb->len = 0; + fb->ptr = fb->buf; + list_add_tail(&fb->list, &fl->block_head); + + return fb; +} + +static struct frag_block *_prepare_frag_block(struct frag_list *fl, int size) +{ + struct frag_block *fb; + + if (size > FRAG_BLOCK_MAX) + BUG(); + + if (list_empty(&fl->block_head)) { + fb = _create_frag_block(fl); + } else { + fb = list_entry(fl->block_head.prev, struct frag_block, list); + if (size > FRAG_BLOCK_MAX - fb->len) + fb = _create_frag_block(fl); + } + + return fb; +} + +static int _read_fmt_frag(struct frag_head *fh, struct fmt_hdr *h, + struct ringbuf *rb) +{ + int r; + int data_len; + int read_len; + struct frag_list *fl; + struct frag_block *fb; + + data_len = h->len - sizeof(struct fmt_hdr); + read_len = data_len + sizeof(hdlc_end); + + _dbg("%s: data %d\n", __func__, data_len); + + fl = _find_frag_list(h->control, fh); + if (!fl) + fl = _create_frag_list(h->control, fh); + + if (!fl) + return -ENOMEM; + + fb = _prepare_frag_block(fl, read_len); + if (!fb) { + if (fl->len == 0) + _destroy_frag_list(fl, fh); + + return -ENOMEM; + } + + r = __read(rb, fb->ptr, read_len); + if (r != read_len) { + _destroy_frag_list(fl, fh); + return -EBADMSG; + } + + fb->ptr += data_len; + fb->len += data_len; + fl->len += data_len; + + _dbg("%s: fl %p len %d fb %p ptr %p len %d\n", __func__, + fl, fl->len, fb, fb->ptr, fb->len); + + return r; +} + +static int _read_fmt_last(struct frag_head *fh, struct fmt_hdr *h, + struct ringbuf *rb, struct net_device *ndev) +{ + int r; + int data_len; + int read_len; + int total_len; + struct sk_buff *skb; + struct frag_list *fl; + char *p; + + total_len = data_len = h->len - sizeof(struct fmt_hdr); + read_len = data_len + sizeof(hdlc_end); + + fl = _find_frag_list(h->control & FMT_ID_MASK, fh); + if (fl) + total_len += fl->len; + + _dbg("%s: total %d data %d\n", __func__, total_len, data_len); + + skb = netdev_alloc_skb(ndev, total_len + + sizeof(struct phonethdr) + sizeof(hdlc_end)); + if (unlikely(!skb)) + return -ENOMEM; + + skb_reserve(skb, sizeof(struct phonethdr)); + + if (fl) + _fill_skb(skb, fl); + + _destroy_frag_list(fl, fh); + + p = skb_put(skb, data_len); + r = __read(rb, p, read_len); + if (r != read_len) { + kfree_skb(skb); + return -EBADMSG; + } + + _phonet_rx(ndev, skb, PN_FMT); + + return r; +} + +static int _read_fmt(struct sipc *si, int inbuf, struct ringbuf *rb) +{ + int r; + char buf[sizeof(struct fmt_hdr) + sizeof(hdlc_start)]; + struct fmt_hdr *h; + u32 tail; + struct net_device *ndev = si->svndev; + + h = (struct fmt_hdr *)&buf[sizeof(hdlc_start)]; + while (inbuf > 0) { + tail = rb->cont->in_tail; + + r = __read(rb, buf, sizeof(buf)); + if (r < sizeof(buf) || + strncmp(buf, hdlc_start, sizeof(hdlc_start))) { + dev_err(&ndev->dev, "Bad message: %c %d\n", buf[0], r); + return -EBADMSG; + } + inbuf -= r; + + if (is_fmt_last(h->control)) + r = _read_fmt_last(&si->frag_map, h, rb, ndev); + else + r = _read_fmt_frag(&si->frag_map, h, rb); + + if (r < 0) { + if (r == -ENOMEM) + rb->cont->in_tail = tail; + + return r; + } + + inbuf -= r; + } + + return 0; +} + +static inline int check_mailbox(u32 mailbox, int idx) +{ + return mailbox & mb_data[idx].mask_send; +} + +static inline void purge_buffer(struct ringbuf *rb) +{ + rb->cont->in_tail = rb->cont->in_head; +} + +int sipc_read(struct sipc *si, u32 mailbox, int *cond) +{ + int r = 0; + int i; + u32 res = 0; + + if (!si) + return -EINVAL; + + r = _get_auth(); + if (r) + return r; + + for (i = 0; i < IPCIDX_MAX; i++) { + int inbuf; + struct ringbuf *rb; + +/* if (!check_mailbox(mailbox, i)) + continue; */ + + rb = &si->rb[i]; + inbuf = CIRC_CNT(rb->cont->in_head, + rb->cont->in_tail, rb->info->size); + if (!inbuf) + continue; + + if (i == IPCIDX_FMT) + _fmt_wakelock_timeout(); + else + _non_fmt_wakelock_timeout(); + + _dbg("%s: %d bytes in %d\n", __func__, inbuf, i); + + r = rb->info->read(si, inbuf, rb); + if (r < 0) { + if (r == -EBADMSG) + purge_buffer(rb); + + dev_err(&si->svndev->dev, "read err %d\n", r); + break; + } + + if (mailbox & mb_data[i].mask_req_ack) + res = mb_data[i].mask_res_ack; + } + + _req_rel_auth(si); + _put_auth(si); + + if (res) + onedram_write_mailbox(MB_DATA(res)); + + *cond = skb_queue_len(&si->rfs_rx); + + return r; +} + +int sipc_rx(struct sipc *si) +{ + int tx_cnt; + struct sk_buff *skb; + + if (!si) + return -EINVAL; + + if (skb_queue_len(&si->rfs_rx) == 0) + return 0; + + tx_cnt = 0; + skb = skb_dequeue(&si->rfs_rx); + while (skb) { + _phonet_rx(si->svndev, skb, PN_RFS); + tx_cnt++; + if (tx_cnt > RFS_TX_RATE) + break; + skb = skb_dequeue(&si->rfs_rx); + } + + return skb_queue_len(&si->rfs_rx); +} + +static inline ssize_t _debug_show_buf(struct sipc *si, char *buf) +{ + int i; + int r; + int inbuf, outbuf; + char *p = buf; + + r = _get_auth(); + if (r) { + p += sprintf(p, "\nGet authority: timed out!\n"); + return p - buf; + } + + p += sprintf(p, "\nHeader info ---------\n"); + + for (i = 0; i < IPCIDX_MAX; i++) { + struct ringbuf *rb = &si->rb[i]; + inbuf = CIRC_CNT(rb->cont->in_head, + rb->cont->in_tail, rb->info->size); + outbuf = CIRC_CNT(rb->cont->out_head, + rb->cont->out_tail, rb->info->size); + p += sprintf(p, "%d\tSize\t%8u\n\tIn\t%8u\t%8u\t%8u\n\tOut\t%8u\t%8u\t%8u\n", + i, rb->info->size, + rb->cont->in_head, rb->cont->in_tail, inbuf, + rb->cont->out_head, rb->cont->out_tail, outbuf); + } + _put_auth(si); + + return p - buf; +} + +static inline ssize_t _debug_show_pdp(struct sipc *si, char *buf) +{ + int i; + char *p = buf; + + p += sprintf(p, "\nPDP count: %d\n", pdp_cnt); + + mutex_lock(&pdp_mutex); + for (i = 0; i < sizeof(pdp_devs)/sizeof(pdp_devs[0]); i++) { + if (pdp_devs[i]) + p += sprintf(p, "pdp%d: %d", i, + netif_queue_stopped(pdp_devs[i])); + } + mutex_unlock(&pdp_mutex); + + return p - buf; +} + +ssize_t sipc_debug_show(struct sipc *si, char *buf) +{ + char *p = buf; + + if (!si || !buf) + return 0; + + p += _debug_show_buf(si, p); + + p += _debug_show_pdp(si, p); + + p += sprintf(p, "\nDebug command -----------\n"); + p += sprintf(p, "R0\tcopy FMT out to in\n"); + p += sprintf(p, "R1\tcopy RAW out to in\n"); + p += sprintf(p, "R2\tcopy RFS out to in\n"); + + return p - buf; +} + +static void test_copy_buf(struct sipc *si, int idx) +{ + struct ringbuf *rb; + + if (idx >= IPCIDX_MAX) + return; + + if (!cp_state) { + printk(KERN_ERR "test_copy_buf : cp_state : %d\n", cp_state); + return; + } + + rb = (struct ringbuf *)&si->rb[idx]; + + memcpy(rb->in_base, rb->out_base, rb->info->size); + rb->cont->in_head = rb->cont->out_head; + rb->cont->in_tail = rb->cont->out_tail; + + rb->cont->out_tail = rb->cont->out_head; + + if (si->queue) + si->queue(MB_DATA(mb_data[idx].mask_send), si->queue_data); +} + +int sipc_debug(struct sipc *si, const char *buf) +{ + int r; + + if (!si || !buf) + return -EINVAL; + + r = _get_auth(); + if (r) + return r; + + switch (buf[0]) { + case 'R': + test_copy_buf(si, buf[1]-'0'); + break; + default: + /* do nothing */ + break; + } + _put_auth(si); + + return 0; +} + +int sipc_whitelist(struct sipc *si, const char *buf, size_t count) +{ + int r; + struct ringbuf *rb; + + printk(KERN_ERR "[%s]\n", __func__); + + if (factory_test_force_sleep) { + printk(KERN_ERR "[%s]factory test\n", __func__); + return count; + } + + if (!si || !buf) + return -EINVAL; + + r = _get_auth(); + if (r) + return r; + + rb = (struct ringbuf *)&si->rb[IPCIDX_FMT]; + + /* write direct full-established-packet to buf */ + r = __write(rb, (u8 *) buf, (unsigned int)count); + + _req_rel_auth(si); + _put_auth(si); + + onedram_write_mailbox(MB_DATA(mb_data[IPCIDX_FMT].mask_send)); + return r; +} + +int sipc_check_skb(struct sipc *si, struct sk_buff *skb) +{ + struct phonethdr *ph; + + ph = pn_hdr(skb); + + if (ph->pn_res == PN_CMD) + return 1; + + return 0; +} + +int sipc_do_cmd(struct sipc *si, struct sk_buff *skb) +{ + if (!si) + return -EINVAL; + + skb_pull(skb, sizeof(struct phonethdr) + 1); + + if (!strncmp("PHONE_ON", skb->data, sizeof("PHONE_ON"))) + return 0; + + return 0; +} + +static int pdp_activate(struct net_device *svndev, int channel) +{ + int idx; + struct net_device *ndev; + + if (!svndev || channel < 1 || channel > PDP_MAX) + return -EINVAL; + + idx = channel - 1; /* start from 0 */ + + mutex_lock(&pdp_mutex); + + if (pdp_devs[idx]) { + mutex_unlock(&pdp_mutex); + return -EBUSY; + } + + ndev = create_pdp(channel, svndev); + if (IS_ERR(ndev)) { + mutex_unlock(&pdp_mutex); + return PTR_ERR(ndev); + } + + pdp_devs[idx] = ndev; + pdp_cnt++; + + mutex_unlock(&pdp_mutex); + + return 0; +} + +static int pdp_deactivate(int channel) +{ + int idx; + + if (channel < 1 || channel > PDP_MAX) + return -EINVAL; + + idx = channel - 1; /* start from 0 */ + + mutex_lock(&pdp_mutex); + + if (!pdp_devs[idx]) { + mutex_unlock(&pdp_mutex); + return -EBUSY; + } + + destroy_pdp(&pdp_devs[idx]); + clear_bit(idx, pdp_bitmap); + pdp_cnt--; + + mutex_unlock(&pdp_mutex); + + return 0; +} + +static ssize_t show_act(struct device *d, + struct device_attribute *attr, char *buf) +{ + int i; + char *p = buf; + + mutex_lock(&pdp_mutex); + + for (i = 0; i < sizeof(pdp_devs)/sizeof(pdp_devs[0]); i++) { + if (pdp_devs[i]) + p += sprintf(p, "%d\n", (i+1)); + } + + mutex_unlock(&pdp_mutex); + + return p - buf; +} + +static ssize_t show_deact(struct device *d, + struct device_attribute *attr, char *buf) +{ + int i; + char *p = buf; + + mutex_lock(&pdp_mutex); + + for (i = 0; i < sizeof(pdp_devs)/sizeof(pdp_devs[0]); i++) { + if (!pdp_devs[i]) + p += sprintf(p, "%d\n", (i+1)); + } + + mutex_unlock(&pdp_mutex); + + return p - buf; +} + +static ssize_t store_act(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + int r; + unsigned long chan; + struct net_device *ndev = to_net_dev(d); + + if (!ndev) + return count; + + r = strict_strtoul(buf, 10, &chan); + if (!r) + r = pdp_activate(ndev, chan); + + if (r) { + dev_err(&ndev->dev, "Failed to activate pdp " + " channel %lu: %d\n", chan, r); + + /* lock for pdp_activate fail - jongmoon.suh */ + return r; + } + + return count; +} + +static ssize_t store_deact(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + int r; + unsigned long chan; + struct net_device *ndev = to_net_dev(d); + + if (!ndev) + return count; + + r = strict_strtoul(buf, 10, &chan); + if (!r) + r = pdp_deactivate(chan); + + if (r) + dev_err(&ndev->dev, "Failed to deactivate pdp" + " channel %lu: %d\n", chan, r); + + return count; +} + +static ssize_t show_suspend(struct device *d, + struct device_attribute *attr, char *buf) +{ + int i; + char *p = buf; + + mutex_lock(&pdp_mutex); + + for (i = 0; i < sizeof(pdp_devs)/sizeof(pdp_devs[0]); i++) { + if (test_bit(i, pdp_bitmap)) + p += sprintf(p, "%d\n", (i+1)); + } + + mutex_unlock(&pdp_mutex); + + return p - buf; +} + +static ssize_t store_suspend(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + int r; + unsigned long chan; + int id; + + r = strict_strtoul(buf, 10, &chan); + if (r) + return count; + + if (chan < 1 || chan > PDP_MAX) + return count; + + id = chan - 1; + + mutex_lock(&pdp_mutex); + + set_bit(id, pdp_bitmap); + + if (pdp_devs[id]) + netif_stop_queue(pdp_devs[id]); + + mutex_unlock(&pdp_mutex); + + return count; +} + +static ssize_t store_resume(struct device *d, + struct device_attribute *attr, const char *buf, size_t count) +{ + int r; + unsigned long chan; + int id; + + r = strict_strtoul(buf, 10, &chan); + if (r) + return count; + + if (chan < 1 || chan > PDP_MAX) + return count; + + id = chan - 1; + + mutex_lock(&pdp_mutex); + + clear_bit(id, pdp_bitmap); + + if (pdp_devs[id]) + netif_wake_queue(pdp_devs[id]); + + mutex_unlock(&pdp_mutex); + + return count; +} + +void sipc_ramdump(struct sipc *si) +{ +#if defined(CONFIG_KERNEL_DEBUG_SEC) + /* silent reset at debug level low */ + if (kernel_sec_get_debug_level() == KERNEL_SEC_DEBUG_LEVEL_LOW) + return; +#endif + _go_dump(si); +} + +#if defined(CONFIG_KERNEL_DEBUG_SEC) +static void _go_dump(struct sipc *si) +{ + int r; + t_kernel_sec_mmu_info mmu_info; + + memset(cp_errmsg, 0, sizeof(cp_errmsg)); + + r = _get_auth(); + if (r) + strcpy(cp_errmsg, ERRMSG); + else { + char *p; + p = (char *)si->map + FATAL_DISP; + memcpy(cp_errmsg, p, sizeof(cp_errmsg)); + } + + printk(KERN_ERR "CP Dump Cause - %s\n", cp_errmsg); + + kernel_sec_set_upload_magic_number(); + kernel_sec_get_mmu_reg_dump(&mmu_info); + kernel_sec_set_upload_cause(UPLOAD_CAUSE_CP_ERROR_FATAL); + kernel_sec_hw_reset(false); + +} +#endif diff --git a/drivers/phone_svn/svnet/sipc4.h b/drivers/phone_svn/svnet/sipc4.h new file mode 100644 index 0000000..40e6714 --- /dev/null +++ b/drivers/phone_svn/svnet/sipc4.h @@ -0,0 +1,267 @@ +/** + * SAMSUNG MODEM IPC header version 4 + * + * Copyright (C) 2010 Samsung Electronics. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __SAMSUNG_IPC_V4_H__ +#define __SAMSUNG_IPC_V4_H__ + +/* IPC4.1 NEW PARTITION MAP + * This map is seen by AP side + + 0x00_0000 =========================================== + MAGIC(4)| ACCESS(4) | RESERVED(8) + 0x00_0010 ------------------------------------------- + FMT_OUT_PTR | FMT_IN_PTR + HEAD(4) | TAIL(4) | HEAD(4) | TAIL(4) + 0x00_0020 ------------------------------------------- + RAW_OUT_PTR | RAW_IN_PTR + HEAD(4) | TAIL(4) | HEAD(4) | TAIL(4) + 0x00_0030 ------------------------------------------- + RFS_OUT_PTR | RFS_IN_PTR + HEAD(4) | TAIL(4) | HEAD(4) | TAIL(4) + 0x00_0040 ------------------------------------------- + RESERVED (4KB - 64B) + 0x00_1000 ------------------------------------------- + CP Fatal Display (160B) + 0x00_10A0 ------------------------------------------- + RESERVED (1MB - 4kb-4kb-4kb - 160B) + 0x0F_E000 ------------------------------------------- + Formatted Out (64KB) + 0x10_E000 ------------------------------------------- + Formatted In (64KB) + 0x11_E000 =========================================== + Raw Out (1MB) + 0x21_E000 =========================================== + Raw In (1MB) + 0x31_E000 =========================================== + RemoteFS Out (1MB) + 0x41_E000 =========================================== + RemoteFS In (1MB) + 0x51_E000 =========================================== + + 0xFF_FFFF =========================================== +*/ + +#if defined(CONFIG_PHONE_IPC_SPI) +#define FMT_OUT 0x0FE000 +#define FMT_IN 0x10E000 +#define FMT_SZ 0x10000 /* 65536 bytes */ + +#define RAW_OUT 0x11E000 +#define RAW_IN 0x21E000 +#define RAW_SZ 0x100000 /* 1 MB */ + +#define RFS_OUT 0x31E000 +#define RFS_IN 0x41E000 +#define RFS_SZ 0x100000 /* 1 MB */ +#else +#if defined(CONFIG_PHONE_IPC_HSI) +#define FMT_OUT 0x0FE000 +#define FMT_IN 0x10E000 +#define FMT_SZ 0x10000 /* 65536 bytes */ + +#define RAW_OUT 0x11E000 +#define RAW_IN 0x21E000 +#define RAW_SZ 0x100000 /* 1 MB */ + +#define RFS_OUT 0x31E000 +#define RFS_IN 0x41E000 +#define RFS_SZ 0x100000 /* 1 MB */ +#endif +#endif + +#define FATAL_DISP 0x001000 +#define FATAL_DISP_SZ 0xA0 /* 160 bytes */ + +#define SIPC_MAP_SIZE (RFS_IN + RFS_SZ) +#define SIPC_NAME "IPCv4.1" + +enum { + IPCIDX_FMT = 0, + IPCIDX_RAW, + IPCIDX_RFS, + IPCIDX_MAX +}; + +struct ringbuf_cont { + u32 out_head; + u32 out_tail; + u32 in_head; + u32 in_tail; +}; + +struct sipc_mapped { /* map to the onedram start addr */ + u32 magic; + u32 access; + u32 reserved[2]; + + struct ringbuf_cont rbcont[IPCIDX_MAX]; +}; + + +#define PN_CMD 0x00 +#define PN_FMT 0x01 +#define PN_RFS 0x41 +#define PN_RAW(chid) (0x20 | (chid)) +#define CHID(x) ((x) & 0x1F) + +#define res_to_ridx(x) ((x) >> 5) + +/* + * IPC Frame Format + */ +#define HDLC_START 0x7F +#define HDLC_END 0x7E + +/* Formatted IPC Frame */ +struct fmt_hdr { + u16 len; + u8 control; +} __packed; + +#define FMT_ID_MASK 0x7F /* Information ID mask */ +#define FMT_ID_SIZE 0x80 /* = 128 ( 0 ~ 127 ) */ +#define FMT_MB_MASK 0x80 /* More bit mask */ + +#define FMT_TX_MIN 5 /* ??? */ + +#define is_fmt_last(x) (!((x) & FMT_MB_MASK)) + +/* RAW IPC Frame */ +struct raw_hdr { + u32 len; + u8 channel; + u8 control; +} __packed; + + +/* RFS IPC Frame */ +struct rfs_hdr { + u32 len; + u8 cmd; + u8 id; +} __packed; + +/* + * RAW frame channel ID + */ +enum { + CHID_0 = 0, + CHID_CSD_VT_DATA, + CHID_PDS_PVT_CONTROL, + CHID_PDS_VT_AUDIO, + CHID_PDS_VT_VIDEO, + CHID_5, /* 5 */ + CHID_6, + CHID_CDMA_DATA, + CHID_PCM_DATA, + CHID_TRANSFER_SCREEN, + CHID_PSD_DATA1, /* 10 */ + CHID_PSD_DATA2, + CHID_PSD_DATA3, + CHID_PSD_DATA4, + CHID_PSD_DATA5, + CHID_PSD_DATA6, /* 15 */ + CHID_PSD_DATA7, + CHID_PSD_DATA8, + CHID_PSD_DATA9, + CHID_PSD_DATA10, + CHID_PSD_DATA11, /* 20 */ + CHID_PSD_DATA12, + CHID_PSD_DATA13, + CHID_PSD_DATA14, + CHID_PSD_DATA15, + CHID_BT_DUN, /* 25 */ + CHID_CIQ_BRIDGE_DATA, + CHID_27, + CHID_CP_LOG1, + CHID_CP_LOG2, + CHID_30, /* 30 */ + CHID_31, + CHID_MAX +}; + +#define PDP_MAX 15 +#define PN_PDP_START PN_RAW(CHID_PSD_DATA1) +#define PN_PDP_END PN_RAW(CHID_PSD_DATA15) + +#define PN_PDP(chid) (0x20 | ((chid) + CHID_PSD_DATA1 - 1)) +#define PDP_ID(res) ((res) - PN_PDP_START) + + +/* + * IPC 4.0 Mailbox message definition + */ +#define MB_VALID 0x0080 +#define MB_COMMAND 0x0040 + +#define MB_CMD(x) (MB_VALID | MB_COMMAND | x) +#define MB_DATA(x) (MB_VALID | x) + +/* + * If not command + */ +#define MBD_SEND_FMT 0x0002 +#define MBD_SEND_RAW 0x0001 +#define MBD_SEND_RFS 0x0100 +#define MBD_REQ_ACK_FMT 0x0020 +#define MBD_REQ_ACK_RAW 0x0010 +#define MBD_REQ_ACK_RFS 0x0400 +#define MBD_RES_ACK_FMT 0x0008 +#define MBD_RES_ACK_RAW 0x0004 +#define MBD_RES_ACK_RFS 0x0200 + +/* + * If command + */ +enum { + MBC_NONE = 0, + MBC_INIT_START, + MBC_INIT_END, + MBC_REQ_ACTIVE, + MBC_RES_ACTIVE, + MBC_TIME_SYNC, + MBC_POWER_OFF, + MBC_RESET, + MBC_PHONE_START, + MBC_ERR_DISPLAY, + MBC_POWER_SAVE, + MBC_NV_REBUILD, + MBC_EMER_DOWN, + MBC_REQ_SEM, + MBC_RES_SEM, + MBC_MAX +}; +#define MBC_MASK 0xFF + +/* CMD_INIT_END extended bit */ +#define CP_BOOT_ONLINE 0x0000 +#define CP_BOOT_AIRPLANE 0x1000 +#define AP_OS_ANDROID 0x0100 +#define AP_OS_WINMOBILE 0x0200 +#define AP_OS_LINUX 0x0300 +#define AP_OS_SYMBIAN 0x0400 + +/* CMD_PHONE_START extended bit */ +#define CP_QUALCOMM 0x0100 +#define CP_INFINEON 0x0200 +#define CP_BROADCOM 0x0300 + + +#endif /* __SAMSUNG_IPC_V4_H__ */ |