From e754d13c1713e247b884af5f210d1c6d62948e85 Mon Sep 17 00:00:00 2001 From: Chris Blair Date: Fri, 4 Nov 2011 13:51:25 +0000 Subject: modem: Add M6718 IPC SPI driver net interface Adds the net device interface from the modem to userspace. ST-Ericsson ID: 369397 ST-Ericsson FOSS-OUT ID: STETL-FOSS-OUT-12224 ST-Ericsson Linux next: NA Change-Id: Ib1cafdc1558305d808870b0fdafa8e7029ec5a6b Signed-off-by: Chris Blair Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/36494 Reviewed-by: QATOOLS Reviewed-by: QABUILD Reviewed-by: Jonas ABERG --- drivers/modem/m6718_spi/modem_driver.c | 55 ++++- drivers/net/Makefile | 1 + drivers/net/m6718_modem_net.c | 333 ++++++++++++++++++++++++++++++ include/linux/modem/m6718_spi/modem_net.h | 50 +++++ 4 files changed, 437 insertions(+), 2 deletions(-) create mode 100644 drivers/net/m6718_modem_net.c create mode 100644 include/linux/modem/m6718_spi/modem_net.h diff --git a/drivers/modem/m6718_spi/modem_driver.c b/drivers/modem/m6718_spi/modem_driver.c index d85e6943c23..623a9191d27 100644 --- a/drivers/modem/m6718_spi/modem_driver.c +++ b/drivers/modem/m6718_spi/modem_driver.c @@ -13,9 +13,15 @@ #include #include #include +#include #include #include "modem_protocol.h" +#ifdef CONFIG_PHONET +static void phonet_rcv_tasklet_func(unsigned long); +static struct tasklet_struct phonet_rcv_tasklet; +#endif + static struct modem_spi_dev modem_driver_data = { .dev = NULL, .ndev = NULL, @@ -37,6 +43,10 @@ static struct modem_spi_dev modem_driver_data = { * * Special handling is given to slave-loopback channels where the data is simply * sent back to the modem on the same channel. + * + * Special handling is given to the ISI channel when PHONET is enabled - the + * phonet tasklet is scheduled in order to pump the received data through the + * net device interface. */ int modem_m6718_spi_receive(struct spi_device *sdev, u8 channel, u32 len, void *data) @@ -95,10 +105,37 @@ int modem_m6718_spi_receive(struct spi_device *sdev, u8 channel, dev_err(&sdev->dev, "failed to queue frame!"); return ret; } + +#ifdef CONFIG_PHONET + if (channel == MODEM_M6718_SPI_CHN_ISI && + modem_driver_data.netdev_flag_up) + tasklet_schedule(&phonet_rcv_tasklet); +#endif return ret; } EXPORT_SYMBOL_GPL(modem_m6718_spi_receive); +static void phonet_rcv_tasklet_func(unsigned long unused) +{ + ssize_t result; + + dev_dbg(modem_driver_data.dev, "receiving frames for phonet\n"); + /* continue receiving while there are frames in the queue */ + for (;;) { + result = modem_net_receive(modem_driver_data.ndev); + if (result == 0) { + dev_dbg(modem_driver_data.dev, + "queue is empty, finished receiving\n"); + break; + } + if (result < 0) { + dev_err(modem_driver_data.dev, + "failed to receive frame from queue!\n"); + break; + } + } +} + static int spi_probe(struct spi_device *sdev) { int result = 0; @@ -133,9 +170,22 @@ static int spi_probe(struct spi_device *sdev) "failed to initialise char interface\n"); goto rollback_modem_get; } + + result = modem_net_init(&modem_driver_data); + if (result < 0) { + dev_err(&sdev->dev, + "failed to initialse net interface\n"); + goto rollback_isa_init; + } + +#ifdef CONFIG_PHONET + tasklet_init(&phonet_rcv_tasklet, phonet_rcv_tasklet_func, 0); +#endif } return result; +rollback_isa_init: + modem_isa_exit(&modem_driver_data); rollback_modem_get: modem_put(modem_driver_data.modem); rollback_protocol_init: @@ -147,6 +197,7 @@ rollback: static int __exit spi_remove(struct spi_device *sdev) { modem_protocol_exit(); + modem_net_exit(&modem_driver_data); modem_isa_exit(&modem_driver_data); return 0; } @@ -167,7 +218,7 @@ static int spi_suspend(struct spi_device *sdev, pm_message_t mesg) busy = modem_protocol_is_busy(sdev); dev_dbg(&sdev->dev, "suspend called, protocol busy:%d\n", busy); if (!busy) - return 0; + return modem_net_suspend(modem_driver_data.ndev); else return -EBUSY; } @@ -179,7 +230,7 @@ static int spi_suspend(struct spi_device *sdev, pm_message_t mesg) static int spi_resume(struct spi_device *sdev) { dev_dbg(&sdev->dev, "resume called\n"); - return 0; + return modem_net_resume(modem_driver_data.ndev); } #endif /* CONFIG_PM */ diff --git a/drivers/net/Makefile b/drivers/net/Makefile index c1d8099a34e..a0f2484368b 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -73,4 +73,5 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/ ifdef CONFIG_PHONET obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o +obj-$(CONFIG_MODEM_M6718_SPI) += m6718_modem_net.o endif diff --git a/drivers/net/m6718_modem_net.c b/drivers/net/m6718_modem_net.c new file mode 100644 index 00000000000..f64a775560b --- /dev/null +++ b/drivers/net/m6718_modem_net.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Chris Blair for ST-Ericsson + * based on u8500_shrm.c + * + * License terms: GNU General Public License (GPL) version 2 + * + * M6718 modem net device interface. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * modem_net_receive() - receive data and copy to user space buffer + * @dev: pointer to the network device structure + * + * Copy data from ISI queue to the user space buffer. + */ +int modem_net_receive(struct net_device *dev) +{ + struct sk_buff *skb; + struct isa_device_context *isadev; + struct message_queue *q; + u32 msgsize; + u32 size = 0; + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + isadev = &modem_spi_dev->isa_context->isadev[MODEM_M6718_SPI_CHN_ISI]; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + dev_dbg(modem_spi_dev->dev, "empty queue!\n"); + return 0; + } + spin_unlock_bh(&q->update_lock); + + msgsize = modem_isa_msg_size(q); + if (msgsize <= 0) + return msgsize; + + /* + * The packet has been retrieved from the transmission + * medium. Build an skb around it, so upper layers can handle it + */ + skb = dev_alloc_skb(msgsize); + if (!skb) { + pr_notice_ratelimited("isa rx: low on mem - packet dropped\n"); + dev->stats.rx_dropped++; + return -ENOMEM; + } + + if ((q->readptr + msgsize) >= q->size) { + size = (q->size - q->readptr); + /* copy first part of msg */ + skb_copy_to_linear_data(skb, + (u8 *)(q->fifo_base + q->readptr), size); + skb_put(skb, size); + + /* copy second part of msg at the top of fifo */ + skb_copy_to_linear_data_offset(skb, size, + (u8 *)(q->fifo_base), (msgsize - size)); + skb_put(skb, msgsize - size); + + } else { + skb_copy_to_linear_data(skb, + (u8 *)(q->fifo_base + q->readptr), msgsize); + skb_put(skb, msgsize); + } + + spin_lock_bh(&q->update_lock); + modem_isa_unqueue_msg(q); + spin_unlock_bh(&q->update_lock); + + skb_reset_mac_header(skb); + __skb_pull(skb, dev->hard_header_len); + /* write metadata and then pass to the receive level */ + skb->dev = dev; + skb->protocol = htons(ETH_P_PHONET); + skb->priority = 0; + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += msgsize; + } else { + dev->stats.rx_dropped++; + } + + return msgsize; +} +EXPORT_SYMBOL_GPL(modem_net_receive); + +static int netdev_isa_open(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + modem_spi_dev->netdev_flag_up = 1; + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_wake_queue(dev); + return 0; +} + +static int netdev_isa_close(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + modem_spi_dev->netdev_flag_up = 0; + netif_stop_queue(dev); + netif_carrier_off(dev); + return 0; +} + +static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct if_phonet_req *req = (struct if_phonet_req *)ifr; + + switch (cmd) { + case SIOCPNGAUTOCONF: + req->ifr_phonet_autoconf.device = PN_DEV_HOST; + return 0; + } + return -ENOIOCTLCMD; +} + +static struct net_device_stats *netdev_isa_stats(struct net_device *dev) +{ + return &dev->stats; +} + +/** + * netdev_isa_write() - write through the net interface + * @skb: pointer to the socket buffer + * @dev: pointer to the network device structure + * + * Copies data(ISI message) from the user buffer to the kernel buffer and + * schedule transfer thread to transmit the message to the modem via FIFO. + */ +static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev) +{ + int err; + int retval = 0; + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + /* + * FIXME: + * U8500 modem requires that Pipe created/enabled Indication should + * be sent from the port corresponding to GPRS socket. + * Also, the U8500 modem does not implement Pipe controller + * which takes care of port manipulations for GPRS traffic. + * + * Now, APE has GPRS socket and the socket for sending + * Indication msgs bound to different ports. + * Phonet stack does not allow an indication msg to be sent + * from GPRS socket, since Phonet stack assumes the presence + * of Pipe controller in modem. + * + * So, due to lack of Pipe controller implementation in the + * U8500 modem, carry out the port manipulation related to + * GPRS traffic here. + * Ideally, it should be done either by Pipe controller in + * modem OR some implementation of Pipe controller on APE side + */ + if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) { + if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND)) + skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX]; + } + + spin_lock_bh(&modem_spi_dev->isa_context->common_tx_lock); + err = modem_m6718_spi_send(modem_spi_dev, MODEM_M6718_SPI_CHN_ISI, + skb->len, skb->data); + if (!err) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + retval = NETDEV_TX_OK; + dev_kfree_skb(skb); + } else { + dev->stats.tx_dropped++; + retval = NETDEV_TX_BUSY; + } + spin_unlock_bh(&modem_spi_dev->isa_context->common_tx_lock); + + return retval; +} + +static const struct net_device_ops modem_netdev_ops = { + .ndo_open = netdev_isa_open, + .ndo_stop = netdev_isa_close, + .ndo_do_ioctl = netdev_isa_ioctl, + .ndo_start_xmit = netdev_isa_write, + .ndo_get_stats = netdev_isa_stats, +}; + +static void net_device_init(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv; + + dev->netdev_ops = &modem_netdev_ops; + dev->header_ops = &phonet_header_ops; + dev->type = ARPHRD_PHONET; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->mtu = PHONET_MAX_MTU; + dev->hard_header_len = MODEM_HLEN; + dev->addr_len = PHONET_ALEN; + dev->tx_queue_len = PN_TX_QUEUE_LEN; + dev->destructor = free_netdev; + dev->dev_addr[0] = PN_LINK_ADDR; + net_iface_priv = netdev_priv(dev); + memset(net_iface_priv, 0 , sizeof(struct modem_spi_net_dev)); +} + +int modem_net_init(struct modem_spi_dev *modem_spi_dev) +{ + struct net_device *nw_device; + struct modem_spi_net_dev *net_iface_priv; + int err; + /* + * keep the same net device name as U8500 to allow userspace clients + * to remain unchanged and use the same interfaces + */ + char *devname = "shrm%d"; + + /* allocate the net device */ + nw_device = modem_spi_dev->ndev = + alloc_netdev(sizeof(struct modem_spi_net_dev), + devname, net_device_init); + if (nw_device == NULL) { + dev_err(modem_spi_dev->dev, + "failed to allocate modem net device\n"); + return -ENOMEM; + } + err = register_netdev(modem_spi_dev->ndev); + if (err) { + dev_err(modem_spi_dev->dev, + "failed to register modem net device: error %d\n", err); + free_netdev(modem_spi_dev->ndev); + return -ENODEV; + } + dev_dbg(modem_spi_dev->dev, "registered modem net device\n"); + + net_iface_priv = (struct modem_spi_net_dev *)netdev_priv(nw_device); + net_iface_priv->modem_spi_dev = modem_spi_dev; + net_iface_priv->iface_num = 0; + return err; +} +EXPORT_SYMBOL_GPL(modem_net_init); + +int modem_net_stop(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +int modem_net_restart(struct net_device *dev) +{ + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_restart); + +int modem_net_start(struct net_device *dev) +{ + struct modem_spi_net_dev *net_iface_priv = + (struct modem_spi_net_dev *)netdev_priv(dev); + struct modem_spi_dev *modem_spi_dev = net_iface_priv->modem_spi_dev; + + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_start_queue(dev); + modem_spi_dev->netdev_flag_up = 1; + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_start); + +int modem_net_suspend(struct net_device *dev) +{ + if (netif_running(dev)) { + netif_stop_queue(dev); + netif_carrier_off(dev); + } + netif_device_detach(dev); + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_suspend); + +int modem_net_resume(struct net_device *dev) +{ + netif_device_attach(dev); + if (netif_running(dev)) { + netif_carrier_on(dev); + netif_wake_queue(dev); + } + return 0; +} +EXPORT_SYMBOL_GPL(modem_net_resume); + +void modem_net_exit(struct modem_spi_dev *modem_spi_dev) +{ + if (modem_spi_dev && modem_spi_dev->ndev) { + unregister_netdev(modem_spi_dev->ndev); + modem_spi_dev->ndev = NULL; + dev_dbg(modem_spi_dev->dev, "removed modem net device\n"); + } +} +EXPORT_SYMBOL_GPL(modem_net_exit); + +MODULE_AUTHOR("Chris Blair "); +MODULE_DESCRIPTION("M6718 modem IPC net device interface"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/modem/m6718_spi/modem_net.h b/include/linux/modem/m6718_spi/modem_net.h new file mode 100644 index 00000000000..521103bf006 --- /dev/null +++ b/include/linux/modem/m6718_spi/modem_net.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Chris Blair for ST-Ericsson + * based on shrm_net.h + * + * License terms: GNU General Public License (GPL) version 2 + * + * Modem IPC net device interface header. + */ +#ifndef _MODEM_NET_H_ +#define _MODEM_NET_H_ + +#include + +#define MODEM_HLEN (1) +#define PHONET_ALEN (1) + +#define PN_PIPE (0xD9) +#define PN_DEV_HOST (0x00) +#define PN_LINK_ADDR (0x26) +#define PN_TX_QUEUE_LEN (3) + +#define RESOURCE_ID_INDEX (3) +#define SRC_OBJ_INDEX (7) +#define MSG_ID_INDEX (9) +#define PIPE_HDL_INDEX (10) +#define NETLINK_MODEM (20) + +/** + * struct modem_spi_net_dev - modem net interface device information + * @modem_spi_dev: pointer to the modem spi device information structure + * @iface_num: flag used to indicate the up/down of netdev + */ +struct modem_spi_net_dev { + struct modem_spi_dev *modem_spi_dev; + unsigned int iface_num; +}; + +int modem_net_init(struct modem_spi_dev *modem_spi_dev); +void modem_net_exit(struct modem_spi_dev *modem_spi_dev); + +int modem_net_receive(struct net_device *dev); +int modem_net_suspend(struct net_device *dev); +int modem_net_resume(struct net_device *dev); +int modem_net_start(struct net_device *dev); +int modem_net_restart(struct net_device *dev); +int modem_net_stop(struct net_device *dev); + +#endif /* _MODEM_NET_H_ */ -- cgit v1.2.3