summaryrefslogtreecommitdiff
path: root/drivers/modem/m6718_spi/netlink.c
blob: 253b19162b1a832f6626c2d14e080b614559bae1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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);
}