exynos-linux-stable/drivers/misc/modem_if/modem_io_device.c
FAROVITUS 2b92eefa41 import G965FXXU7DTAA OSRC
*First release for Android (Q).

Signed-off-by: FAROVITUS <farovitus@gmail.com>
2020-02-04 13:50:09 +02:00

1661 lines
42 KiB
C

/*
* Copyright (C) 2010 Samsung Electronics.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/if_arp.h>
#include <linux/ip.h>
#include <linux/if_ether.h>
#include <linux/etherdevice.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include "modem_prj.h"
#include "modem_utils.h"
static u16 exynos_build_fr_config(struct io_device *iod, struct link_device *ld,
unsigned int count);
static void exynos_build_header(struct io_device *iod, struct link_device *ld,
u8 *buff, u16 cfg, u8 ctl, size_t count);
static ssize_t show_waketime(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int msec;
char *p = buf;
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct io_device *iod = container_of(miscdev, struct io_device,
miscdev);
msec = jiffies_to_msecs(iod->waketime);
p += snprintf(buf, PAGE_SIZE, "raw waketime : %ums\n", msec);
return p - buf;
}
static ssize_t store_waketime(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned long msec;
int ret;
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct io_device *iod = container_of(miscdev, struct io_device,
miscdev);
ret = kstrtoul(buf, 10, &msec);
if (ret)
return count;
iod->waketime = msecs_to_jiffies(msec);
return count;
}
static struct device_attribute attr_waketime =
__ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime);
static ssize_t show_loopback(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct modem_shared *msd =
container_of(miscdev, struct io_device, miscdev)->msd;
unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr;
char *p = buf;
p += snprintf(buf, PAGE_SIZE, "%u.%u.%u.%u\n",
ip[0], ip[1], ip[2], ip[3]);
return p - buf;
}
static ssize_t store_loopback(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct modem_shared *msd =
container_of(miscdev, struct io_device, miscdev)->msd;
msd->loopback_ipaddr = ipv4str_to_be32(buf, count);
return count;
}
static struct device_attribute attr_loopback =
__ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback);
static void iodev_showtxlink(struct io_device *iod, void *args)
{
char **p = (char **)args;
struct link_device *ld = get_current_link(iod);
if (iod->io_typ == IODEV_NET && IS_CONNECTED(iod, ld))
*p += snprintf(*p, PAGE_SIZE, "%s<->%s\n", iod->name, ld->name);
}
static ssize_t show_txlink(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct modem_shared *msd =
container_of(miscdev, struct io_device, miscdev)->msd;
char *p = buf;
iodevs_for_each(msd, iodev_showtxlink, &p);
return p - buf;
}
static ssize_t store_txlink(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
/* don't change without gpio dynamic switching */
return -EINVAL;
}
static struct device_attribute attr_txlink =
__ATTR(txlink, S_IRUGO | S_IWUSR, show_txlink, store_txlink);
static inline void iodev_lock_wlock(struct io_device *iod)
{
if (iod->waketime > 0 && !wake_lock_active(&iod->wakelock))
wake_lock_timeout(&iod->wakelock, iod->waketime);
}
static int netif_flow_ctrl(struct link_device *ld, struct sk_buff *skb)
{
u8 cmd = skb->data[0];
if (cmd == FLOW_CTRL_SUSPEND) {
if (ld->suspend_netif_tx)
goto exit;
ld->suspend_netif_tx = true;
mif_netif_stop(ld);
mif_info("%s: FLOW_CTRL_SUSPEND\n", ld->name);
} else if (cmd == FLOW_CTRL_RESUME) {
if (!ld->suspend_netif_tx)
goto exit;
ld->suspend_netif_tx = false;
mif_netif_wake(ld);
mif_info("%s: FLOW_CTRL_RESUME\n", ld->name);
} else {
mif_info("%s: ERR! invalid command %02X\n", ld->name, cmd);
}
exit:
dev_kfree_skb_any(skb);
return 0;
}
static inline int queue_skb_to_iod(struct sk_buff *skb, struct io_device *iod)
{
struct sk_buff_head *rxq = &iod->sk_rx_q;
skb_queue_tail(rxq, skb);
if (iod->format < IPC_MULTI_RAW && rxq->qlen > MAX_IOD_RXQ_LEN) {
mif_err("%s: %s application may be dead (rxq->qlen %d > %d)\n",
iod->name, iod->app ? iod->app : "corresponding",
rxq->qlen, MAX_IOD_RXQ_LEN);
skb_queue_purge(rxq);
return -ENOSPC;
} else {
mif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen);
wake_up(&iod->wq);
return 0;
}
}
static int rx_drain(struct sk_buff *skb)
{
dev_kfree_skb_any(skb);
return 0;
}
static int rx_loopback(struct sk_buff *skb)
{
struct io_device *iod = skbpriv(skb)->iod;
struct link_device *ld = skbpriv(skb)->ld;
int ret;
ret = ld->send(ld, iod, skb);
if (ret < 0) {
mif_err("%s->%s: ERR! ld->send fail (err %d)\n",
iod->name, ld->name, ret);
}
return ret;
}
static int gather_multi_frame(struct exynos_link_header *hdr,
struct sk_buff *skb)
{
u16 ctrl = hdr->cfg;
struct io_device *iod = skbpriv(skb)->iod;
struct modem_ctl *mc = iod->mc;
struct sk_buff_head *multi_q = &iod->sk_multi_q[exynos_multi_packet_index(ctrl)];
int len = skb->len;
#ifdef DEBUG_MODEM_IF_LINK_RX
/* If there has been no multiple frame with this ID, ... */
if (skb_queue_empty(multi_q)) {
mif_err("%s<-%s: start of multi-frame (pkt_index:%d fr_index:%d len:%d)\n",
iod->name, mc->name, exynos_multi_packet_index(ctrl),
exynos_multi_frame_index(ctrl), hdr->len);
}
#endif
skb_queue_tail(multi_q, skb);
if (!exynos_multi_last(ctrl)) {
/* It is the last frame because the "more" bit is 0. */
mif_err("%s<-%s: recv of multi-frame (CH_ID:%d rcvd:%d)\n",
iod->name, mc->name, hdr->ch_id, skb->len);
} else {
struct sk_buff_head *rxq = &iod->sk_rx_q;
struct sk_buff *skb_new;
struct sk_buff *skb_tmp;
/* The last frame has not arrived yet. */
mif_err("%s<-%s: end multi-frame (CH_ID:%d rcvd:%d)\n",
iod->name, mc->name, hdr->ch_id, skb->len);
skb_new = dev_alloc_skb(SZ_64K);
while(!skb_queue_empty(multi_q)) {
skb_tmp = skb_dequeue(multi_q);
memcpy(skb_put(skb_new, skb_tmp->len), skb_tmp->data, skb_tmp->len);
}
skb_trim(skb_new, skb_new->len);
skb_queue_tail(rxq, skb_new);
skb_queue_head_init(multi_q);
wake_up(&iod->wq);
}
return len;
}
static inline int rx_frame_with_link_header(struct sk_buff *skb)
{
struct exynos_link_header *hdr;
bool single_frame;
/* Remove EXYNOS link header */
hdr = (struct exynos_link_header *)skb->data;
skb_pull(skb, EXYNOS_HEADER_SIZE);
single_frame = exynos_single_frame((u8 *)hdr);
if (single_frame)
return queue_skb_to_iod(skb, skbpriv(skb)->iod);
else
return gather_multi_frame(hdr, skb);
}
static int rx_fmt_ipc(struct sk_buff *skb)
{
if (skbpriv(skb)->lnk_hdr)
return rx_frame_with_link_header(skb);
else
return queue_skb_to_iod(skb, skbpriv(skb)->iod);
}
static int rx_raw_misc(struct sk_buff *skb)
{
struct io_device *iod = skbpriv(skb)->iod;
if (skbpriv(skb)->lnk_hdr) {
/* Remove the EXYNOS link header */
skb_pull(skb, EXYNOS_HEADER_SIZE);
}
queue_skb_to_iod(skb, iod);
wake_up(&iod->wq);
return 0;
}
static int rx_multi_pdp(struct sk_buff *skb)
{
struct link_device *ld = skbpriv(skb)->ld;
struct io_device *iod = skbpriv(skb)->iod;
struct net_device *ndev;
struct iphdr *iphdr;
int ret;
ndev = iod->ndev;
if (!ndev) {
mif_info("%s: ERR! no iod->ndev\n", iod->name);
return -ENODEV;
}
/* Remove the EXYNOS link header */
skb_pull(skb, EXYNOS_HEADER_SIZE);
skb->dev = ndev;
ndev->stats.rx_packets++;
ndev->stats.rx_bytes += skb->len;
/* check the version of IP */
iphdr = (struct iphdr *)skb->data;
if (iphdr->version == IP6VERSION)
skb->protocol = htons(ETH_P_IPV6);
else
skb->protocol = htons(ETH_P_IP);
if (iod->use_handover) {
struct ethhdr *ehdr;
const char source[ETH_ALEN] = SOURCE_MAC_ADDR;
ehdr = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr));
memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN);
memcpy(ehdr->h_source, source, ETH_ALEN);
ehdr->h_proto = skb->protocol;
skb->ip_summed = CHECKSUM_UNNECESSARY;
skb_reset_mac_header(skb);
skb_pull(skb, sizeof(struct ethhdr));
}
if (in_interrupt())
ret = netif_rx(skb);
else
ret = netif_rx_ni(skb);
if (ret != NET_RX_SUCCESS) {
mif_err("%s->%s: ERR! netif_rx fail (err %d)\n",
ld->name, iod->name, ret);
}
return ret;
}
static int rx_demux(struct link_device *ld, struct sk_buff *skb)
{
struct io_device *iod;
u8 ch = skbpriv(skb)->exynos_ch;
if (unlikely(ch == 0)) {
mif_err("%s: ERR! invalid ch# %d\n", ld->name, ch);
return -ENODEV;
}
if (unlikely(ch == EXYNOS_CH_ID_FLOW_CTRL))
return netif_flow_ctrl(ld, skb);
/* IP loopback */
if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr)
ch = EXYNOS_CH_ID_PDP_0;
iod = link_get_iod_with_channel(ld, ch);
if (unlikely(!iod)) {
mif_err("%s: ERR! no iod with ch# %d\n", ld->name, ch);
return -ENODEV;
}
skbpriv(skb)->ld = ld;
skbpriv(skb)->iod = iod;
/* Don't care whether or not DATA_DRAIN_CHANNEL is opened */
if (iod->id == DATA_DRAIN_CHANNEL)
return rx_drain(skb);
/* Don't care whether or not DATA_LOOPBACK_CHANNEL is opened */
if (iod->id == DATA_LOOPBACK_CHANNEL)
return rx_loopback(skb);
/* Print recieved data from CP */
log_ipc_pkt(skb, IODEV, RX);
if (atomic_read(&iod->opened) <= 0) {
mif_err("%s: ERR! %s is not opened\n", ld->name, iod->name);
return -ENODEV;
}
if (exynos_fmt_ch(ch) || exynos_rcs_ch(ch) || exynos_ppt_ch(ch))
return rx_fmt_ipc(skb);
else if (exynos_ps_ch(ch))
return rx_multi_pdp(skb);
else
return rx_raw_misc(skb);
}
/**
* rx_frame_config
* @iod: pointer to an instance of io_device structure
* @ld: pointer to an instance of link_device structure
* @buff: pointer to a buffer in which incoming data is stored
* @size: size of data in the buffer
* @frm: pointer to an instance of exynos_frame_data structure
*
* 1) Checks a config field
* 2) Calculates the length of link layer header in an incoming frame and stores
* the value to "frm->hdr_len"
* 3) Stores the config field to "frm->hdr" and add the size of config field to
* "frm->hdr_rcvd"
*
* Returns the length of a config field that was copied to "frm"
*/
static int rx_frame_config(struct io_device *iod, struct link_device *ld,
u8 *buff, int size, struct exynos_frame_data *frm)
{
int rest;
int rcvd;
if (unlikely(!exynos_start_valid(buff))) {
mif_err("%s->%s: ERR! INVALID config 0x%02x\n",
ld->name, iod->name, buff[0]);
return -EBADMSG;
}
frm->hdr_len = EXYNOS_HEADER_SIZE;
/* Calculate the size of a segment that will be copied */
rest = frm->hdr_len;
rcvd = EXYNOS_FRAG_CONFIG_SIZE;
mif_debug("%s->%s: hdr_len:%d hdr_rcvd:%d rest:%d size:%d rcvd:%d\n",
ld->name, iod->name, frm->hdr_len, frm->hdr_rcvd, rest, size,
rcvd);
/* Copy the config field of an EXYNOS link header to the header buffer */
memcpy(frm->hdr, buff, rcvd);
frm->hdr_rcvd += rcvd;
return rcvd;
}
/**
* rx_frame_prepare_skb
* @iod: pointer to an instance of io_device structure
* @ld: pointer to an instance of link_device structure
* @frm: pointer to an instance of exynos_frame_data structure
*
* 1) Extracts the length of a link frame from the link header in "frm->hdr"
* 2) Allocates an skb
* 3) Calculates the payload size in the link frame
* 4) Calculates the padding size in the link frame
*
* Returns the pointer to an skb
*/
static struct sk_buff *rx_frame_prepare_skb(struct io_device *iod,
struct link_device *ld, struct exynos_frame_data *frm)
{
struct sk_buff *skb;
/* Get the frame length */
frm->len = exynos_get_frame_len(frm->hdr);
/* Allocate an skb */
skb = rx_alloc_skb(frm->len, iod, ld);
if (!skb) {
mif_err("%s->%s: ERR! rx_alloc_skb fail (size %d)\n",
ld->name, iod->name, frm->len);
return NULL;
}
/* Calculates the payload size */
frm->pay_len = frm->len - frm->hdr_len;
/* Calculates the padding size */
if (exynos_padding_exist(frm->hdr))
frm->pad_len = exynos_calc_padding_size(frm->len);
mif_debug("%s->%s: size %d (header:%d payload:%d padding:%d)\n",
ld->name, iod->name, frm->len, frm->hdr_len, frm->pay_len,
frm->pad_len);
return skb;
}
/**
* rx_frame_header
* @iod: pointer to an instance of io_device structure
* @ld: pointer to an instance of link_device structure
* @buff: pointer to a buffer in which incoming data is stored
* @size: size of data in the buffer
* @frm: pointer to an instance of exynos_frame_data structure
*
* 1) Stores a link layer header to "frm->hdr" temporarily while "frm->hdr_rcvd"
* is less than "frm->hdr_len"
* 2) Then,
* Allocates an skb
* Copies the link header from "frm" to "skb"
* Register the skb to receive payload
*
* Returns the size of a segment that was copied to "frm"
*/
static int rx_frame_header(struct io_device *iod, struct link_device *ld,
u8 *buff, int size, struct exynos_frame_data *frm)
{
struct sk_buff *skb;
int rest;
int rcvd;
/* Calculate the size of a segment that will be copied */
rest = frm->hdr_len - frm->hdr_rcvd;
rcvd = min(rest, size);
mif_debug("%s->%s: hdr_len:%d hdr_rcvd:%d rest:%d size:%d rcvd:%d\n",
ld->name, iod->name, frm->hdr_len, frm->hdr_rcvd, rest, size,
rcvd);
/* Copy a segment of an EXYNOS link header to "frm" */
memcpy((frm->hdr + frm->hdr_rcvd), buff, rcvd);
frm->hdr_rcvd += rcvd;
if (frm->hdr_rcvd >= frm->hdr_len) {
/* Prepare an skb with the information in {iod, ld, frm} */
skb = rx_frame_prepare_skb(iod, ld, frm);
if (!skb) {
mif_err("%s->%s: ERR! rx_frame_prepare_skb fail\n",
ld->name, iod->name);
return -ENOMEM;
}
/* Copy an EXYNOS link header from "frm" to "skb" */
memcpy(skb_put(skb, frm->hdr_len), frm->hdr, frm->hdr_len);
/* Register the skb to receive payload */
fragdata(iod, ld)->skb_recv = skb;
}
return rcvd;
}
/**
* rx_frame_payload
* @iod: pointer to an instance of io_device structure
* @ld: pointer to an instance of link_device structure
* @buff: pointer to a buffer in which incoming data is stored
* @size: size of data in the buffer
* @frm: pointer to an instance of exynos_frame_data structure
*
* Stores a link layer payload to "skb"
*
* Returns the size of a segment that was copied to "skb"
*/
static int rx_frame_payload(struct io_device *iod, struct link_device *ld,
u8 *buff, int size, struct exynos_frame_data *frm)
{
struct sk_buff *skb = fragdata(iod, ld)->skb_recv;
int rest;
int rcvd;
/* Calculate the size of a segment that will be copied */
rest = frm->pay_len - frm->pay_rcvd;
rcvd = min(rest, size);
mif_debug("%s->%s: pay_len:%d pay_rcvd:%d rest:%d size:%d rcvd:%d\n",
ld->name, iod->name, frm->pay_len, frm->pay_rcvd, rest, size,
rcvd);
/* Copy an EXYNOS link payload to "skb" */
memcpy(skb_put(skb, rcvd), buff, rcvd);
frm->pay_rcvd += rcvd;
return rcvd;
}
static int rx_frame_padding(struct io_device *iod, struct link_device *ld,
u8 *buff, int size, struct exynos_frame_data *frm)
{
struct sk_buff *skb = fragdata(iod, ld)->skb_recv;
int rest;
int rcvd;
/* Calculate the size of a segment that will be dropped as padding */
rest = frm->pad_len - frm->pad_rcvd;
rcvd = min(rest, size);
mif_debug("%s->%s: pad_len:%d pad_rcvd:%d rest:%d size:%d rcvd:%d\n",
ld->name, iod->name, frm->pad_len, frm->pad_rcvd, rest, size,
rcvd);
/* Copy an EXYNOS link padding to "skb" */
memcpy(skb_put(skb, rcvd), buff, rcvd);
frm->pad_rcvd += rcvd;
return rcvd;
}
static int rx_frame_done(struct io_device *iod, struct link_device *ld,
struct sk_buff *skb)
{
/* Cut off the padding of the current frame */
skb_trim(skb, exynos_get_frame_len(skb->data));
mif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len);
return rx_demux(ld, skb);
}
static int recv_frame_from_buff(struct io_device *iod, struct link_device *ld,
const char *data, unsigned size)
{
struct exynos_frame_data *frm = &fragdata(iod, ld)->f_data;
struct sk_buff *skb;
u8 *buff = (u8 *)data;
int rest = (int)size;
int done = 0;
int err = 0;
mif_debug("%s->%s: size %d (RX state = %s)\n", ld->name, iod->name,
size, get_rx_state_str(iod->curr_rx_state));
while (rest > 0) {
switch (iod->curr_rx_state) {
case IOD_RX_ON_STANDBY:
fragdata(iod, ld)->skb_recv = NULL;
memset(frm, 0, sizeof(struct exynos_frame_data));
done = rx_frame_config(iod, ld, buff, rest, frm);
if (done < 0) {
err = done;
goto err_exit;
}
iod->next_rx_state = IOD_RX_HEADER;
break;
case IOD_RX_HEADER:
done = rx_frame_header(iod, ld, buff, rest, frm);
if (done < 0) {
err = done;
goto err_exit;
}
if (frm->hdr_rcvd >= frm->hdr_len)
iod->next_rx_state = IOD_RX_PAYLOAD;
else
iod->next_rx_state = IOD_RX_HEADER;
break;
case IOD_RX_PAYLOAD:
done = rx_frame_payload(iod, ld, buff, rest, frm);
if (done < 0) {
err = done;
goto err_exit;
}
if (frm->pay_rcvd >= frm->pay_len) {
if (frm->pad_len > 0)
iod->next_rx_state = IOD_RX_PADDING;
else
iod->next_rx_state = IOD_RX_ON_STANDBY;
} else {
iod->next_rx_state = IOD_RX_PAYLOAD;
}
break;
case IOD_RX_PADDING:
done = rx_frame_padding(iod, ld, buff, rest, frm);
if (done < 0) {
err = done;
goto err_exit;
}
if (frm->pad_rcvd >= frm->pad_len)
iod->next_rx_state = IOD_RX_ON_STANDBY;
else
iod->next_rx_state = IOD_RX_PADDING;
break;
default:
mif_err("%s->%s: ERR! INVALID RX state %d\n",
ld->name, iod->name, iod->curr_rx_state);
err = -EINVAL;
goto err_exit;
}
if (iod->next_rx_state == IOD_RX_ON_STANDBY) {
/* A complete frame is in fragdata(iod, ld)->skb_recv. */
skb = fragdata(iod, ld)->skb_recv;
err = rx_frame_done(iod, ld, skb);
if (err < 0)
goto err_exit;
}
buff += done;
rest -= done;
if (rest < 0)
goto err_range;
iod->curr_rx_state = iod->next_rx_state;
}
return size;
err_exit:
if (fragdata(iod, ld)->skb_recv) {
mif_err("%s->%s: ERR! clear frag (size:%d done:%d rest:%d)\n",
ld->name, iod->name, size, done, rest);
dev_kfree_skb_any(fragdata(iod, ld)->skb_recv);
fragdata(iod, ld)->skb_recv = NULL;
}
iod->curr_rx_state = IOD_RX_ON_STANDBY;
return err;
err_range:
mif_err("%s->%s: ERR! size:%d done:%d rest:%d\n",
ld->name, iod->name, size, done, rest);
iod->curr_rx_state = IOD_RX_ON_STANDBY;
return size;
}
/* called from link device when a packet arrives for this io device */
static int io_dev_recv_data_from_link_dev(struct io_device *iod,
struct link_device *ld, const char *data, unsigned int len)
{
struct sk_buff *skb;
int err;
switch (iod->format) {
case IPC_FMT:
case IPC_RAW:
case IPC_RFS:
case IPC_MULTI_RAW:
if (iod->waketime)
wake_lock_timeout(&iod->wakelock, iod->waketime);
err = recv_frame_from_buff(iod, ld, data, len);
if (err < 0) {
mif_err("%s->%s: ERR! recv_frame_from_buff fail(err %d)\n",
ld->name, iod->name, err);
}
return err;
default:
mif_debug("%s->%s: len %d\n", ld->name, iod->name, len);
/* save packet to sk_buff */
skb = rx_alloc_skb(len, iod, ld);
if (!skb) {
mif_info("%s->%s: ERR! rx_alloc_skb fail\n",
ld->name, iod->name);
return -ENOMEM;
}
memcpy(skb_put(skb, len), data, len);
queue_skb_to_iod(skb, iod);
wake_up(&iod->wq);
return len;
}
}
/* called from link device when a packet arrives for this io device */
static int io_dev_recv_skb_from_link_dev(struct io_device *iod,
struct link_device *ld, struct sk_buff *skb)
{
int err;
if (iod->waketime)
wake_lock_timeout(&iod->wakelock, iod->waketime);
/* Cut off the padding of the current frame */
skb_trim(skb, exynos_get_frame_len(skb->data));
mif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len);
err = rx_demux(ld, skb);
if (err < 0)
mif_err("%s->%s: ERR! recv_frame_from_skb fail "
"(err %d)\n", ld->name, iod->name, err);
return err;
}
/* called from link device when a packet arrives fo this io device */
static int io_dev_recv_skb_single_from_link_dev(struct io_device *iod,
struct link_device *ld, struct sk_buff *skb)
{
int err;
iodev_lock_wlock(iod);
if (skbpriv(skb)->lnk_hdr && ld->aligned)
skb_trim(skb, exynos_get_frame_len(skb->data));
err = rx_demux(ld, skb);
if (err < 0)
mif_err_limited("%s<-%s: ERR! rx_demux fail (err %d)\n",
iod->name, ld->name, err);
return err;
}
/* 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)
{
struct modem_ctl *mc = iod->mc;
int old_state = mc->phone_state;
if (old_state != state) {
mc->phone_state = state;
mif_err("%s state changed (%s -> %s)\n", mc->name,
get_cp_state_str(old_state), get_cp_state_str(state));
}
if (state == STATE_CRASH_RESET || state == STATE_CRASH_EXIT ||
state == STATE_NV_REBUILDING || state == STATE_CRASH_WATCHDOG)
wake_up(&iod->wq);
}
/**
* io_dev_sim_state_changed
* @iod: IPC's io_device
* @sim_online: SIM is online?
*/
static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online)
{
if (atomic_read(&iod->opened) == 0) {
mif_info("%s: ERR! not opened\n", iod->name);
} else if (iod->mc->sim_state.online == sim_online) {
mif_info("%s: SIM state not changed\n", iod->name);
} else {
iod->mc->sim_state.online = sim_online;
iod->mc->sim_state.changed = true;
mif_info("%s: SIM state changed {online %d, changed %d}\n",
iod->name, iod->mc->sim_state.online,
iod->mc->sim_state.changed);
wake_up(&iod->wq);
}
}
static void iodev_dump_status(struct io_device *iod, void *args)
{
if (iod->format == IPC_RAW && iod->io_typ == IODEV_NET) {
struct link_device *ld = get_current_link(iod);
mif_com_log(iod->mc->msd, "%s: %s\n", iod->name, ld->name);
}
}
static int misc_open(struct inode *inode, struct file *filp)
{
struct io_device *iod = to_io_device(filp->private_data);
struct modem_shared *msd = iod->msd;
struct link_device *ld;
int ref_cnt;
int ret;
filp->private_data = (void *)iod;
list_for_each_entry(ld, &msd->link_dev_list, list) {
if (IS_CONNECTED(iod, ld) && ld->init_comm) {
ret = ld->init_comm(ld, iod);
if (ret < 0) {
mif_err("%s<->%s: ERR! init_comm fail(%d)\n",
iod->name, ld->name, ret);
return ret;
}
}
}
ref_cnt = atomic_inc_return(&iod->opened);
mif_err("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm);
return 0;
}
static int misc_release(struct inode *inode, struct file *filp)
{
struct io_device *iod = (struct io_device *)filp->private_data;
struct modem_shared *msd = iod->msd;
struct link_device *ld;
int ref_cnt;
if (iod->id != EXYNOS_CH_ID_BOOT || strcmp(current->comm, "cbd") == 0)
skb_queue_purge(&iod->sk_rx_q);
list_for_each_entry(ld, &msd->link_dev_list, list) {
if (IS_CONNECTED(iod, ld) && ld->terminate_comm)
ld->terminate_comm(ld, iod);
}
ref_cnt = atomic_dec_return(&iod->opened);
mif_err("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm);
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;
struct modem_ctl *mc = iod->mc;
static int cnt = 0;
poll_wait(filp, &iod->wq, wait);
if (!skb_queue_empty(&iod->sk_rx_q) && mc->phone_state != STATE_OFFLINE)
return POLLIN | POLLRDNORM;
if (mc->phone_state == STATE_CRASH_RESET
|| mc->phone_state == STATE_CRASH_EXIT
|| mc->phone_state == STATE_NV_REBUILDING
|| mc->phone_state == STATE_CRASH_WATCHDOG
|| mc->sim_state.changed) {
if (iod->format == IPC_RAW) {
msleep(20);
return 0;
}
if (iod->format == IPC_DUMP)
return 0;
if ((++cnt % 1000) == 0) {
mif_err("Loading CP Dump File...\n");
cnt = 0;
return POLLHUP | POLLERR;
}
return POLLHUP;
} else {
return 0;
}
}
static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct io_device *iod = (struct io_device *)filp->private_data;
struct link_device *ld = get_current_link(iod);
struct modem_ctl *mc = iod->mc;
int p_state;
char *buff;
void __user *user_buff;
unsigned long size;
switch (cmd) {
case IOCTL_MODEM_ON:
if (mc->ops.modem_on) {
mif_err("%s: IOCTL_MODEM_ON\n", iod->name);
return mc->ops.modem_on(mc);
}
mif_err("%s: !mc->ops.modem_on\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_OFF:
if (mc->ops.modem_off) {
mif_err("%s: IOCTL_MODEM_OFF\n", iod->name);
return mc->ops.modem_off(mc);
}
mif_err("%s: !mc->ops.modem_off\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_RESET:
if (mc->ops.modem_reset) {
mif_err("%s: IOCTL_MODEM_RESET\n", iod->name);
return mc->ops.modem_reset(mc);
}
mif_err("%s: !mc->ops.modem_reset\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_BOOT_ON:
if (mc->ops.modem_boot_on) {
mif_err("%s: IOCTL_MODEM_BOOT_ON\n", iod->name);
return mc->ops.modem_boot_on(mc);
}
mif_err("%s: !mc->ops.modem_boot_on\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_BOOT_OFF:
if (mc->ops.modem_boot_off) {
mif_err("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name);
return mc->ops.modem_boot_off(mc);
}
mif_err("%s: !mc->ops.modem_boot_off\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_BOOT_DONE:
mif_err("%s: IOCTL_MODEM_BOOT_DONE\n", iod->name);
if (mc->ops.modem_boot_done)
return mc->ops.modem_boot_done(mc);
return 0;
case IOCTL_MODEM_STATUS:
mif_debug("%s: IOCTL_MODEM_STATUS\n", iod->name);
p_state = mc->phone_state;
if (p_state != STATE_ONLINE) {
mif_err("%s: IOCTL_MODEM_STATUS (state %s)\n",
iod->name, get_cp_state_str(p_state));
}
if (mc->sim_state.changed) {
int s_state = mc->sim_state.online ?
STATE_SIM_ATTACH : STATE_SIM_DETACH;
mc->sim_state.changed = false;
return s_state;
}
if (p_state == STATE_NV_REBUILDING)
mc->phone_state = STATE_ONLINE;
return p_state;
case IOCTL_MODEM_DL_START:
if (ld->dload_start) {
mif_info("%s: IOCTL_MODEM_DL_START\n", iod->name);
return ld->dload_start(ld, iod);
}
mif_err("%s: !ld->dload_start\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_FW_UPDATE:
if (ld->firm_update) {
mif_info("%s: IOCTL_MODEM_FW_UPDATE\n", iod->name);
return ld->firm_update(ld, iod, arg);
}
mif_err("%s: !ld->firm_update\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_FORCE_CRASH_EXIT:
if (mc->ops.modem_force_crash_exit) {
mif_err("%s: IOCTL_MODEM_FORCE_CRASH_EXIT\n",
iod->name);
return mc->ops.modem_force_crash_exit(mc);
}
mif_err("%s: !mc->ops.modem_force_crash_exit\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_DUMP_RESET:
if (mc->ops.modem_dump_reset) {
mif_info("%s: IOCTL_MODEM_DUMP_RESET\n", iod->name);
return mc->ops.modem_dump_reset(mc);
}
mif_err("%s: !mc->ops.modem_dump_reset\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_DUMP_START:
if (mc->ops.modem_dump_start) {
mif_err("%s: IOCTL_MODEM_DUMP_START\n", iod->name);
return mc->ops.modem_dump_start(mc);
} else if (ld->dump_start) {
mif_err("%s: IOCTL_MODEM_DUMP_START\n", iod->name);
return ld->dump_start(ld, iod);
}
mif_err("%s: !dump_start\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_RAMDUMP_START:
if (ld->dump_start) {
mif_info("%s: IOCTL_MODEM_RAMDUMP_START\n", iod->name);
return ld->dump_start(ld, iod);
}
mif_err("%s: !ld->dump_start\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_DUMP_UPDATE:
if (ld->dump_update) {
mif_info("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name);
return ld->dump_update(ld, iod, arg);
}
mif_err("%s: !ld->dump_update\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_RAMDUMP_STOP:
if (ld->dump_finish) {
mif_info("%s: IOCTL_MODEM_RAMDUMP_STOP\n", iod->name);
return ld->dump_finish(ld, iod, arg);
}
mif_err("%s: !ld->dump_finish\n", iod->name);
return -EINVAL;
case IOCTL_MODEM_CP_UPLOAD:
buff = iod->msd->cp_crash_info + strlen(CP_CRASH_TAG);
user_buff = (void __user *)arg;
mif_info("%s: IOCTL_MODEM_CP_UPLOAD\n", iod->name);
memmove(iod->msd->cp_crash_info, CP_CRASH_TAG,
strlen(CP_CRASH_TAG));
if (arg) {
if (copy_from_user(buff, user_buff, MAX_CPINFO_SIZE))
return -EFAULT;
}
panic(iod->msd->cp_crash_info);
return 0;
case IOCTL_MODEM_PROTOCOL_SUSPEND:
mif_info("%s: IOCTL_MODEM_PROTOCOL_SUSPEND\n", iod->name);
if (iod->format == IPC_MULTI_RAW) {
iodevs_for_each(iod->msd, iodev_netif_stop, 0);
return 0;
}
return -EINVAL;
case IOCTL_MODEM_PROTOCOL_RESUME:
mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n", iod->name);
if (iod->format != IPC_MULTI_RAW) {
iodevs_for_each(iod->msd, iodev_netif_wake, 0);
return 0;
}
return -EINVAL;
case IOCTL_MIF_LOG_DUMP:
user_buff = (void __user *)arg;
iodevs_for_each(iod->msd, iodev_dump_status, 0);
size = MAX_MIF_BUFF_SIZE;
if (copy_to_user(user_buff, &size, sizeof(unsigned long)))
return -EFAULT;
mif_dump_log(mc->msd, iod);
return 0;
case IOCTL_SHMEM_FULL_DUMP:
mif_info("%s: IOCTL_SHMEM_FULL_DUMP\n", iod->name);
if (ld->shmem_dump)
return ld->shmem_dump(ld, iod, arg);
else
return -EINVAL;
case IOCTL_VSS_FULL_DUMP:
mif_info("%s: IOCTL_VSS_FULL_DUMP\n", iod->name);
if (ld->vss_dump)
return ld->vss_dump(ld, iod, arg);
else
return -EINVAL;
case IOCTL_ACPM_FULL_DUMP:
mif_info("%s: IOCTL_ACPM_FULL_DUMP\n", iod->name);
if (ld->acpm_dump)
return ld->acpm_dump(ld, iod, arg);
else
return -EINVAL;
case IOCTL_MODEM_GET_SHMEM_INFO:
mif_debug("<%s> IOCTL_MODEM_GET_SHMEM_INFO\n", iod->name);
return 0;
/* Below codes will be used next time.
return iod->mc->ops.modem_get_meminfo(iod->mc, arg);
*/
case IOCTL_SEC_CP_INIT:
if (ld->sec_init) {
mif_info("%s: IOCTL_MODEM_SEC_INIT\n", iod->name);
return ld->sec_init(ld, iod, arg);
}
return -EINVAL;
case IOCTL_CHECK_SECURITY:
if (ld->check_security) {
mif_info("%s: IOCTL_MODEM_CHECK_SECURITY\n", iod->name);
return ld->check_security(ld, iod, arg);
}
mif_err("%s: !ld->check_security\n", iod->name);
return -EINVAL;
case IOCTL_XMIT_BIN:
if (ld->xmit_bin)
return ld->xmit_bin(ld, iod, arg);
return -EINVAL;
case IOCTL_MODEM_CRASH_REASON:
if (ld->crash_reason) {
mif_info("%s: IOCTL_MODEM_CRASH_REASON\n", iod->name);
return ld->crash_reason(ld, iod, arg);
}
mif_err("%s: !ld->crash_reason\n", iod->name);
return -EINVAL;
default:
/* If you need to handle the ioctl for specific link device,
* then assign the link ioctl handler to ld->ioctl
* It will be call for specific link ioctl */
if (ld->ioctl)
return ld->ioctl(ld, iod, cmd, arg);
mif_info("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd);
return -EINVAL;
}
return 0;
}
static ssize_t misc_write(struct file *filp, const char __user *data,
size_t count, loff_t *fpos)
{
struct io_device *iod = (struct io_device *)filp->private_data;
struct link_device *ld = get_current_link(iod);
struct modem_ctl *mc = iod->mc;
struct sk_buff *skb;
u8 *buff;
int ret;
u32 copied = 0, tot_frame = 0, copied_frm = 0;
u32 remains, alloc_size;
size_t headroom;
size_t tailroom;
size_t tx_bytes;
u16 fr_cfg;
if (iod->format <= IPC_RFS && iod->id == 0)
return -EINVAL;
if (unlikely(!cp_online(mc)) && exynos_ipc_ch(iod->id)) {
mif_debug("%s: ERR! %s->state == %s\n",
iod->name, mc->name, get_cp_state_str(mc->phone_state));
return -EPERM;
}
if (iod->link_header) {
fr_cfg = exynos_build_fr_config(iod, ld, count);
headroom = EXYNOS_HEADER_SIZE;
} else {
fr_cfg = 0;
headroom = 0;
tailroom = 0;
}
while (copied < count) {
remains = count - copied;
tx_bytes = (remains > SZ_2K - headroom) ? SZ_2K - headroom : remains;
if (iod->format >= IPC_BOOT)
tx_bytes = count;
if (ld->aligned)
tailroom = exynos_calc_padding_size(headroom + tx_bytes);
else
tailroom = 0;
alloc_size = headroom + tx_bytes + tailroom;
skb = alloc_skb(alloc_size, GFP_KERNEL);
if (!skb) {
mif_info("%s: ERR! alloc_skb fail (tx_bytes:%ld)\n",
iod->name, tx_bytes);
return -ENOMEM;
}
/* Build EXYNOS link header*/
if (fr_cfg) {
buff = skb_put(skb, headroom);
exynos_build_header(iod, ld, buff, fr_cfg, 0, tx_bytes);
/* modify next link header for multiframe */
if (((fr_cfg >> 8) & EXYNOS_SINGLE_MASK) != EXYNOS_SINGLE_MASK)
fr_cfg = modify_next_frame(fr_cfg);
}
/* Store IPC message */
buff = skb_put(skb, tx_bytes);
if (copy_from_user(buff, data + copied, tx_bytes)) {
mif_err("%s->%s: ERR! copy_from_user fail (count %ld)\n",
iod->name, ld->name, tx_bytes);
dev_kfree_skb_any(skb);
return -EFAULT;
}
/* Apply padding */
if (tailroom)
skb_put(skb, tailroom);
copied += tx_bytes;
tot_frame += alloc_size;
/* Store the IO device, the link device, etc. */
skbpriv(skb)->iod = iod;
skbpriv(skb)->ld = ld;
skbpriv(skb)->lnk_hdr = iod->link_header;
skbpriv(skb)->exynos_ch = iod->id;
log_ipc_pkt(skb, IODEV, TX);
ret = ld->send(ld, iod, skb);
if (ret < 0) {
mif_info("%s->%s: ERR! ld->send fail (err %d, tx_bytes %ld)\n",
iod->name, ld->name, ret, tx_bytes);
return ret;
}
copied_frm += ret;
}
if (copied_frm != tot_frame) {
mif_info("%s->%s: WARNING! ret %d != tx_bytes %d (count %d)\n",
iod->name, ld->name, ret, copied_frm, tot_frame);
}
return count;
}
static ssize_t misc_read(struct file *filp, char *buf, size_t count,
loff_t *fpos)
{
struct io_device *iod = (struct io_device *)filp->private_data;
struct sk_buff_head *rxq = &iod->sk_rx_q;
struct sk_buff *skb;
int copied = 0;
if (skb_queue_empty(rxq)) {
mif_info("%s: ERR! no data in rxq\n", iod->name);
return 0;
}
skb = skb_dequeue(rxq);
if (unlikely(!skb)) {
mif_info("%s: No data in RXQ\n", iod->name);
return 0;
}
#ifdef DEBUG_MODEM_IF_IODEV
log_ipc_pkt(skb, IODEV, RX);
#endif
copied = skb->len > count ? count : skb->len;
if (copy_to_user(buf, skb->data, copied)) {
mif_err("%s: ERR! copy_to_user fail\n", iod->name);
dev_kfree_skb_any(skb);
return -EFAULT;
}
mif_debug("%s: data:%d copied:%d qlen:%d\n",
iod->name, skb->len, copied, rxq->qlen);
if (skb->len > count) {
skb_pull(skb, count);
skb_queue_head(rxq, skb);
} else {
dev_kfree_skb_any(skb);
}
return copied;
}
static const struct file_operations misc_io_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.poll = misc_poll,
.unlocked_ioctl = misc_ioctl,
.compat_ioctl = misc_ioctl,
.write = misc_write,
.read = misc_read,
};
static int vnet_open(struct net_device *ndev)
{
struct vnet *vnet = netdev_priv(ndev);
mif_err("%s\n", vnet->iod->name);
netif_start_queue(ndev);
atomic_inc(&vnet->iod->opened);
return 0;
}
static int vnet_stop(struct net_device *ndev)
{
struct vnet *vnet = netdev_priv(ndev);
mif_err("%s\n", vnet->iod->name);
atomic_dec(&vnet->iod->opened);
netif_stop_queue(ndev);
skb_queue_purge(&vnet->iod->sk_rx_q);
return 0;
}
static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct vnet *vnet = netdev_priv(ndev);
struct io_device *iod = vnet->iod;
struct link_device *ld = get_current_link(iod);
struct sk_buff *skb_new = skb;
struct iphdr *ip_header = (struct iphdr *)skb->data;
struct modem_ctl *mc = iod->mc;
int ret;
unsigned headroom;
unsigned tailroom;
size_t count = skb->len;
size_t tx_bytes;
u8 *buff;
u16 fr_cfg;
if (unlikely(!cp_online(mc))) {
if (!netif_queue_stopped(ndev))
netif_stop_queue(ndev);
/* just drop the tx packet and return tx_ok */
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
}
/* When use `handover' with Network Bridge,
* user -> bridge device(rmnet0) -> real rmnet(xxxx_rmnet0) -> here.
* bridge device is ethernet device unlike xxxx_rmnet(net device).
* We remove the an ethernet header of skb before using skb->len,
* because bridge device added an ethernet header to skb.
*/
if (iod->use_handover) {
if (iod->id >= EXYNOS_CH_ID_PDP_0 &&
iod->id <= EXYNOS_CH_ID_PDP_9)
skb_pull(skb, sizeof(struct ethhdr));
}
if (iod->link_header) {
fr_cfg = exynos_build_fr_config(iod, ld, count);
headroom = EXYNOS_HEADER_SIZE;
if (ld->aligned)
tailroom = exynos_calc_padding_size(EXYNOS_HEADER_SIZE + count);
else
tailroom = 0;
} else {
fr_cfg = 0;
headroom = 0;
tailroom = 0;
}
tx_bytes = headroom + count + tailroom;
if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) {
mif_debug("%s: skb_copy_expand needed\n", iod->name);
skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC);
if (!skb_new) {
mif_info("%s: ERR! skb_copy_expand fail\n", iod->name);
return NETDEV_TX_BUSY;
}
dev_kfree_skb_any(skb);
}
/* Build EXYNOS link header*/
buff = skb_push(skb_new, headroom);
if (fr_cfg)
exynos_build_header(iod, ld, buff, fr_cfg, 0, count);
/* IP loop-back */
ip_header = (struct iphdr *)skb->data;
if (iod->msd->loopback_ipaddr &&
ip_header->daddr == iod->msd->loopback_ipaddr) {
swap(ip_header->saddr, ip_header->daddr);
buff[EXYNOS_CH_ID_OFFSET] = DATA_LOOPBACK_CHANNEL;
}
if (tailroom)
skb_put(skb_new, tailroom);
skbpriv(skb_new)->iod = iod;
skbpriv(skb_new)->ld = ld;
skbpriv(skb_new)->lnk_hdr = iod->link_header;
skbpriv(skb_new)->exynos_ch = iod->id;
ret = ld->send(ld, iod, skb_new);
if (ret < 0) {
netif_stop_queue(ndev);
mif_info("%s->%s: ERR! ld->send fail (err %d, tx_bytes %ld)\n",
iod->name, ld->name, ret, tx_bytes);
return NETDEV_TX_BUSY;
}
if (ret != tx_bytes) {
mif_info("%s->%s: WARNING! ret %d != tx_bytes %ld (count %ld)\n",
iod->name, ld->name, ret, tx_bytes, count);
}
ndev->stats.tx_packets++;
ndev->stats.tx_bytes += count;
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;
}
static u16 exynos_build_fr_config(struct io_device *iod, struct link_device *ld,
unsigned int count)
{
u16 fr_cfg = 0;
u8 frames = 0;
u8 *packet_index = &iod->packet_index;
if (iod->format > IPC_DUMP)
return 0;
if (iod->format >= IPC_BOOT)
return fr_cfg |= (EXYNOS_SINGLE_MASK << 8);
if ((count + EXYNOS_HEADER_SIZE) <= SZ_2K) {
fr_cfg |= (EXYNOS_SINGLE_MASK << 8);
} else {
frames = count / (SZ_2K - EXYNOS_HEADER_SIZE);
frames = (count % (SZ_2K - EXYNOS_HEADER_SIZE)) ? frames : frames - 1;
fr_cfg |= ((EXYNOS_MULTI_START_MASK | (0x3f & ++*packet_index)) << 8) | frames;
}
return fr_cfg;
}
static void exynos_build_header(struct io_device *iod, struct link_device *ld,
u8 *buff, u16 cfg, u8 ctl, size_t count)
{
u16 *exynos_header = (u16 *)(buff + EXYNOS_START_OFFSET);
u16 *frame_seq = (u16 *)(buff + EXYNOS_FRAME_SEQ_OFFSET);
u16 *frag_cfg = (u16 *)(buff + EXYNOS_FRAG_CONFIG_OFFSET);
u16 *size = (u16 *)(buff + EXYNOS_LEN_OFFSET);
struct exynos_seq_num *seq_num = &(iod->seq_num);
*exynos_header = EXYNOS_START_MASK;
*frame_seq = ++seq_num->frame_cnt;
*frag_cfg = cfg;
*size = (u16)(EXYNOS_HEADER_SIZE + count);
buff[EXYNOS_CH_ID_OFFSET] = iod->id;
if (cfg == EXYNOS_SINGLE_MASK)
*frag_cfg = cfg;
buff[EXYNOS_CH_SEQ_OFFSET] = ++seq_num->ch_cnt[iod->id];
}
int exynos_init_io_device(struct io_device *iod)
{
int ret = 0;
int i;
struct vnet *vnet;
if (iod->attrs & IODEV_ATTR(ATTR_NO_LINK_HEADER))
iod->link_header = false;
else
iod->link_header = true;
/* Get modem state from modem control device */
iod->modem_state_changed = io_dev_modem_state_changed;
iod->sim_state_changed = io_dev_sim_state_changed;
/* Get data from link device */
mif_debug("%s: init\n", iod->name);
iod->recv = io_dev_recv_data_from_link_dev;
iod->recv_skb = io_dev_recv_skb_from_link_dev;
iod->recv_skb_single = io_dev_recv_skb_single_from_link_dev;
/* Register misc or net device */
switch (iod->io_typ) {
case IODEV_MISC:
init_waitqueue_head(&iod->wq);
skb_queue_head_init(&iod->sk_rx_q);
iod->miscdev.minor = MISC_DYNAMIC_MINOR;
iod->miscdev.name = iod->name;
iod->miscdev.fops = &misc_io_fops;
ret = misc_register(&iod->miscdev);
if (ret)
mif_info("%s: ERR! misc_register failed - %d\n", iod->name, ret);
break;
case IODEV_NET:
skb_queue_head_init(&iod->sk_rx_q);
if (iod->use_handover)
iod->ndev = alloc_netdev(0, iod->name, NET_NAME_UNKNOWN,
vnet_setup_ether);
else
iod->ndev = alloc_netdev(0, iod->name, NET_NAME_UNKNOWN,
vnet_setup);
if (!iod->ndev) {
mif_info("%s: ERR! alloc_netdev fail\n", iod->name);
return -ENOMEM;
}
ret = register_netdev(iod->ndev);
if (ret) {
mif_info("%s: ERR! register_netdev fail\n", iod->name);
free_netdev(iod->ndev);
}
mif_debug("iod 0x%p\n", iod);
vnet = netdev_priv(iod->ndev);
mif_debug("vnet 0x%p\n", vnet);
vnet->iod = iod;
break;
case IODEV_DUMMY:
skb_queue_head_init(&iod->sk_rx_q);
iod->miscdev.minor = MISC_DYNAMIC_MINOR;
iod->miscdev.name = iod->name;
iod->miscdev.fops = &misc_io_fops;
ret = misc_register(&iod->miscdev);
if (ret)
mif_info("%s: ERR! misc_register fail\n", iod->name);
ret = device_create_file(iod->miscdev.this_device,
&attr_waketime);
if (ret)
mif_info("%s: ERR! device_create_file fail\n",
iod->name);
ret = device_create_file(iod->miscdev.this_device,
&attr_loopback);
if (ret)
mif_err("failed to create `loopback file' : %s\n",
iod->name);
ret = device_create_file(iod->miscdev.this_device,
&attr_txlink);
if (ret)
mif_err("failed to create `txlink file' : %s\n",
iod->name);
break;
default:
mif_info("%s: ERR! wrong io_type %d\n", iod->name, iod->io_typ);
return -EINVAL;
}
for (i = 0; i < NUM_EXYNOS_MULTI_FRAME_IDS; i++)
skb_queue_head_init(&iod->sk_multi_q[i]);
return ret;
}