diff options
-rw-r--r-- | drivers/modem/m6718_spi/Makefile | 3 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/modem_netlink.h | 20 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/netlink.c | 182 | ||||
-rw-r--r-- | drivers/modem/m6718_spi/protocol.c | 2 |
4 files changed, 206 insertions, 1 deletions
diff --git a/drivers/modem/m6718_spi/Makefile b/drivers/modem/m6718_spi/Makefile index b3898c62662..5cb265b00ac 100644 --- a/drivers/modem/m6718_spi/Makefile +++ b/drivers/modem/m6718_spi/Makefile @@ -5,7 +5,8 @@ ifeq ($(CONFIG_MODEM_M6718_SPI_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif -m6718_modem_spi-objs := modem_driver.o protocol.o util.o queue.o debug.o +m6718_modem_spi-objs := modem_driver.o protocol.o util.o queue.o debug.o \ + netlink.o ifeq ($(CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE),y) m6718_modem_spi-objs += modem_state.o diff --git a/drivers/modem/m6718_spi/modem_netlink.h b/drivers/modem/m6718_spi/modem_netlink.h new file mode 100644 index 00000000000..19e123d9b12 --- /dev/null +++ b/drivers/modem/m6718_spi/modem_netlink.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson + * + * License terms: GNU General Public License (GPL) version 2 + * + * Modem IPC driver protocol interface header: + * netlink related functionality. + */ +#ifndef _MODEM_NETLINK_H_ +#define _MODEM_NETLINK_H_ + +#include "modem_protocol.h" + +bool ipc_create_netlink_socket(struct ipc_link_context *context); +void ipc_broadcast_modem_online(struct ipc_link_context *context); +void ipc_broadcast_modem_reset(struct ipc_link_context *context); + +#endif /* _MODEM_NETLINK_H_ */ diff --git a/drivers/modem/m6718_spi/netlink.c b/drivers/modem/m6718_spi/netlink.c new file mode 100644 index 00000000000..253b19162b1 --- /dev/null +++ b/drivers/modem/m6718_spi/netlink.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) ST-Ericsson SA 2010,2011 + * + * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson + * based on shrm_protocol.c + * + * License terms: GNU General Public License (GPL) version 2 + * + * U9500 <-> M6718 IPC protocol implementation using SPI: + * netlink related functionality + */ +#include <linux/netlink.h> +#include <linux/spi/spi.h> +#include <linux/modem/m6718_spi/modem_net.h> +#include <linux/modem/m6718_spi/modem_char.h> +#include "modem_protocol.h" +#include "modem_private.h" +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE +#include "modem_state.h" +#endif + +static struct sock *netlink_sk; +struct modem_spi_dev *modem_dev; + +#define MAX_PAYLOAD 1024 + +/* + * Netlink broadcast message values: this must correspond to those values + * expected by userspace for the appropriate message. + */ +enum netlink_msg_id { + NETLINK_MODEM_RESET = 1, + NETLINK_MODEM_QUERY_STATE, + NETLINK_USER_REQUEST_MODEM_RESET, + NETLINK_MODEM_STATUS_ONLINE, + NETLINK_MODEM_STATUS_OFFLINE +}; + +static void netlink_multicast_tasklet(unsigned long data) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + enum netlink_msg_id nlmsg = (enum netlink_msg_id)data; + + if (netlink_sk == NULL) { + pr_err("could not send multicast, no socket\n"); + return; + } + + /* prepare netlink message */ + skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC); + if (!skb) { + pr_err("failed to allocate socket buffer\n"); + return; + } + + if (nlmsg == NETLINK_MODEM_RESET) + modem_isa_reset(modem_dev); + + nlh = (struct nlmsghdr *)skb->data; + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + nlh->nlmsg_pid = 0; /* from kernel */ + nlh->nlmsg_flags = 0; + *(int *)NLMSG_DATA(nlh) = nlmsg; + skb_put(skb, MAX_PAYLOAD); + /* sender is in group 1<<0 */ + NETLINK_CB(skb).pid = 0; /* from kernel */ + /* to mcast group 1<<0 */ + NETLINK_CB(skb).dst_group = 1; + + /* multicast the message to all listening processes */ + pr_debug("sending netlink multicast message %d\n", nlmsg); + netlink_broadcast(netlink_sk, skb, 0, 1, GFP_ATOMIC); + +} + +static void send_unicast(int dst_pid) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + + if (netlink_sk == NULL) { + pr_err("could not send unicast, no socket\n"); + return; + } + + /* prepare the message for unicast */ + skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_KERNEL); + if (!skb) { + pr_err("failed to allocate socket buffer\n"); + return; + } + + nlh = (struct nlmsghdr *)skb->data; + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + nlh->nlmsg_pid = 0; /* from kernel */ + nlh->nlmsg_flags = 0; + + if (modem_m6718_spi_is_boot_done()) { + pr_debug("sending netlink unicast message %d\n", + NETLINK_MODEM_STATUS_ONLINE); + *(int *)NLMSG_DATA(nlh) = NETLINK_MODEM_STATUS_ONLINE; + } else { + pr_debug("sending netlink unicast message %d\n", + NETLINK_MODEM_STATUS_OFFLINE); + *(int *)NLMSG_DATA(nlh) = NETLINK_MODEM_STATUS_OFFLINE; + } + + skb_put(skb, MAX_PAYLOAD); + /* sender is in group 1<<0 */ + NETLINK_CB(skb).pid = 0; /* from kernel */ + NETLINK_CB(skb).dst_group = 0; + + /* unicast the message to the querying process */ + netlink_unicast(netlink_sk, skb, dst_pid, MSG_DONTWAIT); +} + +static void netlink_receive(struct sk_buff *skb) +{ + struct nlmsghdr *nlh = NULL; + int msg; + + nlh = (struct nlmsghdr *)skb->data; + msg = *((int *)(NLMSG_DATA(nlh))); + switch (msg) { + case NETLINK_MODEM_QUERY_STATE: + send_unicast(nlh->nlmsg_pid); + break; + case NETLINK_USER_REQUEST_MODEM_RESET: + pr_info("user requested modem reset!\n"); +#ifdef CONFIG_DEBUG_FS + if (l1_context.msr_disable) { + pr_info("MSR is disabled, ignoring reset request\n"); + break; + } +#endif +#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE + modem_state_force_reset(); +#else + pr_err("modestate integration is not enabled in IPC, " + "unable to reset modem\n"); +#endif + break; + default: + pr_debug("ignoring invalid netlink message\n"); + break; + } +} + +bool ipc_create_netlink_socket(struct ipc_link_context *context) +{ + if (netlink_sk != NULL) + return true; + + netlink_sk = netlink_kernel_create(NULL, NETLINK_MODEM, 1, + netlink_receive, NULL, THIS_MODULE); + if (netlink_sk == NULL) { + dev_err(&context->sdev->dev, + "failed to create netlink socket\n"); + return false; + } + modem_dev = spi_get_drvdata(context->sdev); + return true; +} + +DECLARE_TASKLET(modem_online_tasklet, netlink_multicast_tasklet, + NETLINK_MODEM_STATUS_ONLINE); +DECLARE_TASKLET(modem_reset_tasklet, netlink_multicast_tasklet, + NETLINK_MODEM_RESET); + +void ipc_broadcast_modem_online(struct ipc_link_context *context) +{ + dev_info(&context->sdev->dev, "broadcast modem online event!\n"); + tasklet_schedule(&modem_online_tasklet); +} + +void ipc_broadcast_modem_reset(struct ipc_link_context *context) +{ + dev_info(&context->sdev->dev, "broadcast modem reset event!\n"); + tasklet_schedule(&modem_reset_tasklet); +} + diff --git a/drivers/modem/m6718_spi/protocol.c b/drivers/modem/m6718_spi/protocol.c index a8e3ce42389..86b124120ce 100644 --- a/drivers/modem/m6718_spi/protocol.c +++ b/drivers/modem/m6718_spi/protocol.c @@ -13,6 +13,7 @@ #include "modem_util.h" #include "modem_queue.h" #include "modem_debug.h" +#include "modem_netlink.h" #ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE #include <linux/workqueue.h> @@ -326,6 +327,7 @@ int modem_protocol_probe(struct spi_device *sdev) ipc_queue_init(context); ipc_dbg_debugfs_link_init(context); ipc_dbg_throughput_link_init(context); + ipc_create_netlink_socket(context); /* * For link0 (the handshake link) we force a state transition now so |