blob: 843ddc24f41cd9bc514adb6c36a8e2b336c6d00a [file] [log] [blame]
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -07001/*
2 * Greybus "AP" message loop handling
3 *
4 * Copyright 2014 Google Inc.
Alex Eldera46e9672014-12-12 12:08:42 -06005 * Copyright 2014 Linaro Ltd.
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -07006 *
7 * Released under the GPLv2 only.
8 */
9
10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11
12#include <linux/types.h>
13#include <linux/module.h>
14#include <linux/moduleparam.h>
15#include <linux/kernel.h>
16#include <linux/slab.h>
17#include <linux/uaccess.h>
Greg Kroah-Hartman45f36782014-09-14 11:40:35 -070018#include <linux/workqueue.h>
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070019#include <linux/device.h>
Greg Kroah-Hartmanb9b2a462014-08-31 17:43:38 -070020#include "svc_msg.h"
Alex Elder05ad1892014-09-09 13:55:03 -050021#include "greybus_manifest.h"
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070022#include "greybus.h"
23
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070024struct ap_msg {
25 u8 *data;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -070026 size_t size;
27 struct greybus_host_device *hd;
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -070028 struct work_struct event;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070029};
30
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -070031static struct workqueue_struct *ap_workqueue;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070032
Matt Porter710ecb02014-09-18 15:25:41 -040033static struct svc_msg *svc_msg_alloc(enum svc_function_id id)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070034{
35 struct svc_msg *svc_msg;
36
37 svc_msg = kzalloc((sizeof *svc_msg), GFP_KERNEL);
38 if (!svc_msg)
39 return NULL;
40
Matt Porter710ecb02014-09-18 15:25:41 -040041 // FIXME - verify we are only sending function IDs we should be
42 svc_msg->header.function_id = id;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070043 return svc_msg;
44}
45
46static void svc_msg_free(struct svc_msg *svc_msg)
47{
48 kfree(svc_msg);
49}
50
51static int svc_msg_send(struct svc_msg *svc_msg, struct greybus_host_device *hd)
52{
53 int retval;
54
55 // FIXME - Do we need to do more than just pass it to the hd and then
56 // free it?
Alex Elder0db32a62014-09-24 05:16:14 -050057 retval = hd->driver->submit_svc(svc_msg, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070058
59 svc_msg_free(svc_msg);
60 return retval;
61}
62
63
Greg Kroah-Hartman1db0a5f2014-12-12 17:10:17 -050064int svc_set_route_send(struct gb_bundle *bundle,
Viresh Kumara4040ab2014-11-19 17:24:57 +053065 struct greybus_host_device *hd)
66{
67 struct svc_msg *svc_msg;
68
69 svc_msg = svc_msg_alloc(SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT);
70 if (!svc_msg)
71 return -ENOMEM;
72
Viresh Kumara4040ab2014-11-19 17:24:57 +053073 svc_msg->header.message_type = SVC_MSG_DATA;
74 svc_msg->header.payload_length =
75 cpu_to_le16(sizeof(struct svc_function_unipro_set_route));
Greg Kroah-Hartman1db0a5f2014-12-12 17:10:17 -050076 svc_msg->management.set_route.device_id = bundle->device_id;
Viresh Kumara4040ab2014-11-19 17:24:57 +053077
78 return svc_msg_send(svc_msg, hd);
79}
80
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070081static void svc_handshake(struct svc_function_handshake *handshake,
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -070082 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070083{
84 struct svc_msg *svc_msg;
85
Viresh Kumar64e69292014-11-19 17:24:58 +053086 if (payload_length != sizeof(*handshake)) {
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -070087 dev_err(hd->parent,
88 "Illegal size of svc handshake message %d\n",
89 payload_length);
90 return;
91 }
92
Matt Portere94e1712014-09-18 15:25:42 -040093 /* A new SVC communication channel, let's verify a supported version */
Johan Hovold0b7534b2015-03-19 16:46:14 +010094 if ((handshake->version_major != GREYBUS_VERSION_MAJOR) ||
Matt Portere94e1712014-09-18 15:25:42 -040095 (handshake->version_minor != GREYBUS_VERSION_MINOR)) {
Johan Hovold0b7534b2015-03-19 16:46:14 +010096 dev_warn(hd->parent,
97 "received invalid greybus version %u.%u\n",
Matt Portere94e1712014-09-18 15:25:42 -040098 handshake->version_major, handshake->version_minor);
99 return;
100 }
101
102 /* Validate that the handshake came from the SVC */
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700103 if (handshake->handshake_type != SVC_HANDSHAKE_SVC_HELLO) {
104 /* we don't know what to do with this, log it and return */
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700105 dev_dbg(hd->parent, "received invalid handshake type %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700106 handshake->handshake_type);
107 return;
108 }
109
110 /* Send back a AP_HELLO message */
111 svc_msg = svc_msg_alloc(SVC_FUNCTION_HANDSHAKE);
112 if (!svc_msg)
113 return;
114
Matt Porterbe5064c2014-09-22 15:51:49 -0400115 svc_msg->header.message_type = SVC_MSG_DATA;
116 svc_msg->header.payload_length =
Viresh Kumar64e69292014-11-19 17:24:58 +0530117 cpu_to_le16(sizeof(*handshake));
Matt Porterbe5064c2014-09-22 15:51:49 -0400118 svc_msg->handshake.version_major = GREYBUS_VERSION_MAJOR;
119 svc_msg->handshake.version_minor = GREYBUS_VERSION_MINOR;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700120 svc_msg->handshake.handshake_type = SVC_HANDSHAKE_AP_HELLO;
Alex Elder525f1462014-10-22 02:04:31 -0500121
122 (void)svc_msg_send(svc_msg, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700123}
124
125static void svc_management(struct svc_function_unipro_management *management,
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700126 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700127{
Greg Kroah-Hartman4ab9b3c2014-12-19 14:56:31 -0800128 struct gb_interface *intf;
Alex Elderc41b4f12014-10-22 02:04:32 -0500129 int ret;
Matt Porter6232b072014-10-21 22:43:31 -0400130
Viresh Kumar64e69292014-11-19 17:24:58 +0530131 if (payload_length != sizeof(*management)) {
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700132 dev_err(hd->parent,
133 "Illegal size of svc management message %d\n",
134 payload_length);
135 return;
136 }
137
Matt Porter98f4ab22014-10-21 01:52:27 -0400138 switch (management->management_packet_type) {
Alex Elder2d5e4fa2014-10-22 05:36:18 -0500139 case SVC_MANAGEMENT_AP_ID:
140 hd->device_id = management->ap_id.device_id;
141 break;
Matt Porter6232b072014-10-21 22:43:31 -0400142 case SVC_MANAGEMENT_LINK_UP:
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530143 intf = gb_interface_find(hd, management->link_up.interface_id);
Greg Kroah-Hartman4ab9b3c2014-12-19 14:56:31 -0800144 if (!intf) {
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530145 dev_err(hd->parent, "Interface ID %d not found\n",
146 management->link_up.interface_id);
Matt Porter6232b072014-10-21 22:43:31 -0400147 return;
148 }
Viresh Kumarbb97ea82015-04-01 20:32:01 +0530149 ret = gb_bundles_init(intf, management->link_up.device_id);
Johan Hovoldfe4c0e52015-03-19 16:46:16 +0100150 if (ret) {
Greg Kroah-Hartman4ab9b3c2014-12-19 14:56:31 -0800151 dev_err(hd->parent,
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530152 "error %d initializing bundles for interface %hhu\n",
153 ret, management->link_up.interface_id);
Johan Hovoldfe4c0e52015-03-19 16:46:16 +0100154 return;
155 }
Matt Porter6232b072014-10-21 22:43:31 -0400156 break;
Matt Porter98f4ab22014-10-21 01:52:27 -0400157 default:
158 dev_err(hd->parent, "Unhandled UniPro management message\n");
159 }
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700160}
161
162static void svc_hotplug(struct svc_function_hotplug *hotplug,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700163 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700164{
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530165 u8 interface_id = hotplug->interface_id;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700166
167 switch (hotplug->hotplug_event) {
168 case SVC_HOTPLUG_EVENT:
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530169 /* Add a new interface to the system */
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700170 if (payload_length < 0x03) {
Johan Hovold69bae892015-03-19 16:46:15 +0100171 /* Hotplug message is at least 3 bytes big */
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700172 dev_err(hd->parent,
173 "Illegal size of svc hotplug message %d\n",
174 payload_length);
175 return;
176 }
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530177 dev_dbg(hd->parent, "interface id %d added\n", interface_id);
Viresh Kumar51b5d8d2015-05-20 17:33:51 +0530178 gb_interface_add(hd, interface_id, hotplug->data,
Greg Kroah-Hartman13e6aac2014-12-19 14:56:35 -0800179 payload_length - 0x02);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700180 break;
181
182 case SVC_HOTUNPLUG_EVENT:
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530183 /* Remove a interface from the system */
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700184 if (payload_length != 0x02) {
185 /* Hotunplug message is only 2 bytes big */
186 dev_err(hd->parent,
187 "Illegal size of svc hotunplug message %d\n",
188 payload_length);
189 return;
190 }
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530191 dev_dbg(hd->parent, "interface id %d removed\n", interface_id);
Viresh Kumar51b5d8d2015-05-20 17:33:51 +0530192 gb_interface_remove(hd, interface_id);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700193 break;
194
195 default:
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700196 dev_err(hd->parent,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700197 "Received invalid hotplug message type %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700198 hotplug->hotplug_event);
199 break;
200 }
201}
202
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700203static void svc_power(struct svc_function_power *power,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700204 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700205{
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530206 u8 interface_id = power->interface_id;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700207
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700208 /*
209 * The AP is only allowed to get a Battery Status message, not a Battery
210 * Status Request
211 */
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700212 if (power->power_type != SVC_POWER_BATTERY_STATUS) {
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700213 dev_err(hd->parent, "Received invalid power type %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700214 power->power_type);
215 return;
216 }
217
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700218 /*
219 * As struct struct svc_function_power_battery_status_request is 0 bytes
220 * big, we can just check the union of the whole structure to validate
221 * the size of this message.
222 */
Viresh Kumar64e69292014-11-19 17:24:58 +0530223 if (payload_length != sizeof(*power)) {
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700224 dev_err(hd->parent,
225 "Illegal size of svc power message %d\n",
226 payload_length);
227 return;
228 }
229
Viresh Kumarc9d9d0d2015-04-01 20:31:58 +0530230 dev_dbg(hd->parent, "power status for interface id %d is %d\n",
231 interface_id, power->status.status);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700232
233 // FIXME - do something with the power information, like update our
234 // battery information...
235}
236
237static void svc_epm(struct svc_function_epm *epm,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700238 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700239{
240 /* What? An AP should not get this message */
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700241 dev_err(hd->parent, "Got an EPM message???\n");
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700242}
243
244static void svc_suspend(struct svc_function_suspend *suspend,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700245 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700246{
247 /* What? An AP should not get this message */
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700248 dev_err(hd->parent, "Got an suspend message???\n");
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700249}
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700250
Viresh Kumare2dabb72014-11-19 17:24:56 +0530251static struct svc_msg *convert_ap_message(struct ap_msg *ap_msg)
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700252{
253 struct svc_msg *svc_msg;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700254 struct svc_msg_header *header;
Viresh Kumare2dabb72014-11-19 17:24:56 +0530255 struct greybus_host_device *hd = ap_msg->hd;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700256
257 svc_msg = (struct svc_msg *)ap_msg->data;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700258 header = &svc_msg->header;
259
260 /* Validate the message type */
261 if (header->message_type != SVC_MSG_DATA) {
262 dev_err(hd->parent, "message type %d received?\n",
263 header->message_type);
264 return NULL;
265 }
266
267 /*
268 * The validation of the size of the message buffer happens in each
269 * svc_* function, due to the different types of messages, keeping the
270 * logic for each message only in one place.
271 */
Greg Kroah-Hartman43cc32a2014-09-07 13:51:12 -0700272
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700273 return svc_msg;
274}
275
Greg Kroah-Hartmanb57b0622014-09-13 12:18:09 -0700276static void ap_process_event(struct work_struct *work)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700277{
278 struct svc_msg *svc_msg;
279 struct greybus_host_device *hd;
Greg Kroah-Hartmanb57b0622014-09-13 12:18:09 -0700280 struct ap_msg *ap_msg;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700281 int payload_length;
Greg Kroah-Hartmanb57b0622014-09-13 12:18:09 -0700282
283 ap_msg = container_of(work, struct ap_msg, event);
284 hd = ap_msg->hd;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700285
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700286 /* Turn the "raw" data into a real message */
Viresh Kumare2dabb72014-11-19 17:24:56 +0530287 svc_msg = convert_ap_message(ap_msg);
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700288 if (!svc_msg)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700289 return;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700290
291 payload_length = le16_to_cpu(svc_msg->header.payload_length);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700292
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700293 /* Look at the message to figure out what to do with it */
Matt Porter710ecb02014-09-18 15:25:41 -0400294 switch (svc_msg->header.function_id) {
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700295 case SVC_FUNCTION_HANDSHAKE:
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700296 svc_handshake(&svc_msg->handshake, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700297 break;
298 case SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT:
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700299 svc_management(&svc_msg->management, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700300 break;
301 case SVC_FUNCTION_HOTPLUG:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700302 svc_hotplug(&svc_msg->hotplug, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700303 break;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700304 case SVC_FUNCTION_POWER:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700305 svc_power(&svc_msg->power, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700306 break;
307 case SVC_FUNCTION_EPM:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700308 svc_epm(&svc_msg->epm, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700309 break;
310 case SVC_FUNCTION_SUSPEND:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700311 svc_suspend(&svc_msg->suspend, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700312 break;
313 default:
Matt Porter710ecb02014-09-18 15:25:41 -0400314 dev_err(hd->parent, "received invalid SVC function ID %d\n",
315 svc_msg->header.function_id);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700316 }
317
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700318 /* clean the message up */
319 kfree(ap_msg->data);
320 kfree(ap_msg);
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700321}
322
Alex Elder51c75fd2014-09-26 20:55:35 -0500323int greybus_svc_in(struct greybus_host_device *hd, u8 *data, int size)
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700324{
325 struct ap_msg *ap_msg;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700326
327 /*
328 * Totally naive copy the message into a new structure that we slowly
329 * create and add it to the list. Let's get this working, the odds of
330 * this being any "slow path" for AP messages is really low at this
331 * point in time, but you never know, so this comment is here to point
332 * out that maybe we should use a slab allocator, or even just not copy
333 * the data, but use it directly and force the urbs to be "new" each
334 * time.
335 */
336
337 /* Note - this can, and will, be called in interrupt context. */
338 ap_msg = kmalloc(sizeof(*ap_msg), GFP_ATOMIC);
339 if (!ap_msg)
340 return -ENOMEM;
341 ap_msg->data = kmalloc(size, GFP_ATOMIC);
342 if (!ap_msg->data) {
343 kfree(ap_msg);
344 return -ENOMEM;
345 }
346 memcpy(ap_msg->data, data, size);
347 ap_msg->size = size;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700348 ap_msg->hd = hd;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700349
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700350 INIT_WORK(&ap_msg->event, ap_process_event);
351 queue_work(ap_workqueue, &ap_msg->event);
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700352
353 return 0;
354}
Alex Elder0db32a62014-09-24 05:16:14 -0500355EXPORT_SYMBOL_GPL(greybus_svc_in);
Greg Kroah-Hartmand94a44a2014-09-01 14:39:34 -0700356
Greg Kroah-Hartman45f36782014-09-14 11:40:35 -0700357int gb_ap_init(void)
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700358{
Greg Kroah-Hartmane0b179e2015-01-22 20:33:41 +0800359 ap_workqueue = alloc_ordered_workqueue("greybus_ap", 0);
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700360 if (!ap_workqueue)
361 return -ENOMEM;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700362
363 return 0;
364}
365
Greg Kroah-Hartman45f36782014-09-14 11:40:35 -0700366void gb_ap_exit(void)
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700367{
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700368 destroy_workqueue(ap_workqueue);
Viresh Kumarf66832da2014-11-14 17:25:01 +0530369 ap_workqueue = NULL;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700370}
371
372