blob: a8cd7e7cdd60eb49cbfe389b5e716837410c0679 [file] [log] [blame]
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -07001/*
2 * Greybus "AP" message loop handling
3 *
4 * Copyright 2014 Google Inc.
5 *
6 * Released under the GPLv2 only.
7 */
8
9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10
11#include <linux/types.h>
12#include <linux/module.h>
13#include <linux/moduleparam.h>
14#include <linux/kernel.h>
15#include <linux/slab.h>
16#include <linux/uaccess.h>
Greg Kroah-Hartman45f36782014-09-14 11:40:35 -070017#include <linux/workqueue.h>
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070018#include <linux/device.h>
Greg Kroah-Hartmanb9b2a462014-08-31 17:43:38 -070019#include "svc_msg.h"
Alex Elder05ad1892014-09-09 13:55:03 -050020#include "greybus_manifest.h"
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070021#include "greybus.h"
22
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070023struct ap_msg {
24 u8 *data;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -070025 size_t size;
26 struct greybus_host_device *hd;
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -070027 struct work_struct event;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070028};
29
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -070030static struct workqueue_struct *ap_workqueue;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -070031
Matt Porter710ecb02014-09-18 15:25:41 -040032static struct svc_msg *svc_msg_alloc(enum svc_function_id id)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070033{
34 struct svc_msg *svc_msg;
35
36 svc_msg = kzalloc((sizeof *svc_msg), GFP_KERNEL);
37 if (!svc_msg)
38 return NULL;
39
Matt Porter710ecb02014-09-18 15:25:41 -040040 // FIXME - verify we are only sending function IDs we should be
41 svc_msg->header.function_id = id;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070042 return svc_msg;
43}
44
45static void svc_msg_free(struct svc_msg *svc_msg)
46{
47 kfree(svc_msg);
48}
49
50static int svc_msg_send(struct svc_msg *svc_msg, struct greybus_host_device *hd)
51{
52 int retval;
53
54 // FIXME - Do we need to do more than just pass it to the hd and then
55 // free it?
Alex Elder0db32a62014-09-24 05:16:14 -050056 retval = hd->driver->submit_svc(svc_msg, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070057
58 svc_msg_free(svc_msg);
59 return retval;
60}
61
62
63static void svc_handshake(struct svc_function_handshake *handshake,
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -070064 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070065{
66 struct svc_msg *svc_msg;
67
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -070068 if (payload_length != sizeof(struct svc_function_handshake)) {
69 dev_err(hd->parent,
70 "Illegal size of svc handshake message %d\n",
71 payload_length);
72 return;
73 }
74
Matt Portere94e1712014-09-18 15:25:42 -040075 /* A new SVC communication channel, let's verify a supported version */
76 if ((handshake->version_major != GREYBUS_VERSION_MAJOR) &&
77 (handshake->version_minor != GREYBUS_VERSION_MINOR)) {
78 dev_dbg(hd->parent, "received invalid greybus version %d:%d\n",
79 handshake->version_major, handshake->version_minor);
80 return;
81 }
82
83 /* Validate that the handshake came from the SVC */
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070084 if (handshake->handshake_type != SVC_HANDSHAKE_SVC_HELLO) {
85 /* we don't know what to do with this, log it and return */
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -070086 dev_dbg(hd->parent, "received invalid handshake type %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -070087 handshake->handshake_type);
88 return;
89 }
90
91 /* Send back a AP_HELLO message */
92 svc_msg = svc_msg_alloc(SVC_FUNCTION_HANDSHAKE);
93 if (!svc_msg)
94 return;
95
Matt Porterbe5064c2014-09-22 15:51:49 -040096 svc_msg->header.function_id = SVC_FUNCTION_HANDSHAKE;
97 svc_msg->header.message_type = SVC_MSG_DATA;
98 svc_msg->header.payload_length =
99 cpu_to_le16(sizeof(struct svc_function_handshake));
100 svc_msg->handshake.version_major = GREYBUS_VERSION_MAJOR;
101 svc_msg->handshake.version_minor = GREYBUS_VERSION_MINOR;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700102 svc_msg->handshake.handshake_type = SVC_HANDSHAKE_AP_HELLO;
Alex Elder525f1462014-10-22 02:04:31 -0500103
104 (void)svc_msg_send(svc_msg, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700105}
106
Alex Elder525f1462014-10-22 02:04:31 -0500107int svc_set_route_send(struct gb_interface *interface,
Matt Porter060b93d2014-10-22 02:06:09 -0400108 struct greybus_host_device *hd)
109{
110 struct svc_msg *svc_msg;
111
112 svc_msg = svc_msg_alloc(SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT);
113 if (!svc_msg)
Alex Elder525f1462014-10-22 02:04:31 -0500114 return -ENOMEM;
Matt Porter060b93d2014-10-22 02:06:09 -0400115
116 svc_msg->header.function_id = SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT;
117 svc_msg->header.message_type = SVC_MSG_DATA;
118 svc_msg->header.payload_length =
119 cpu_to_le16(sizeof(struct svc_function_unipro_set_route));
120 svc_msg->management.set_route.device_id = interface->device_id;
Alex Elder525f1462014-10-22 02:04:31 -0500121
122 return svc_msg_send(svc_msg, hd);
Matt Porter060b93d2014-10-22 02:06:09 -0400123}
124
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700125static 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{
Matt Porter6232b072014-10-21 22:43:31 -0400128 struct gb_module *module;
Alex Elderc41b4f12014-10-22 02:04:32 -0500129 int ret;
Matt Porter6232b072014-10-21 22:43:31 -0400130
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700131 if (payload_length != sizeof(struct svc_function_unipro_management)) {
132 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:
143 module = gb_module_find(hd, management->link_up.module_id);
144 if (!module) {
145 dev_err(hd->parent, "Module ID %d not found\n",
146 management->link_up.module_id);
147 return;
148 }
Alex Elderc41b4f12014-10-22 02:04:32 -0500149 ret = gb_module_interface_init(module,
150 management->link_up.interface_id,
151 management->link_up.device_id);
152 if (ret)
Alex Elder6b099382014-11-05 16:03:12 -0600153 dev_err(hd->parent, "error %d initializing "
Alex Elderc41b4f12014-10-22 02:04:32 -0500154 "module %hhu interface %hhu\n",
155 ret, management->link_up.module_id,
Matt Porter6232b072014-10-21 22:43:31 -0400156 management->link_up.interface_id);
Matt Porter6232b072014-10-21 22:43:31 -0400157 break;
Matt Porter98f4ab22014-10-21 01:52:27 -0400158 default:
159 dev_err(hd->parent, "Unhandled UniPro management message\n");
160 }
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700161}
162
163static void svc_hotplug(struct svc_function_hotplug *hotplug,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700164 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700165{
166 u8 module_id = hotplug->module_id;
167
168 switch (hotplug->hotplug_event) {
169 case SVC_HOTPLUG_EVENT:
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700170 /* Add a new module to the system */
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700171 if (payload_length < 0x03) {
172 /* Hotplug message is at lest 3 bytes big */
173 dev_err(hd->parent,
174 "Illegal size of svc hotplug message %d\n",
175 payload_length);
176 return;
177 }
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700178 dev_dbg(hd->parent, "module id %d added\n", module_id);
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700179 gb_add_module(hd, module_id, hotplug->data,
180 payload_length - 0x02);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700181 break;
182
183 case SVC_HOTUNPLUG_EVENT:
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700184 /* Remove a module from the system */
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700185 if (payload_length != 0x02) {
186 /* Hotunplug message is only 2 bytes big */
187 dev_err(hd->parent,
188 "Illegal size of svc hotunplug message %d\n",
189 payload_length);
190 return;
191 }
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700192 dev_dbg(hd->parent, "module id %d removed\n", module_id);
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700193 gb_remove_module(hd, module_id);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700194 break;
195
196 default:
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700197 dev_err(hd->parent,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700198 "Received invalid hotplug message type %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700199 hotplug->hotplug_event);
200 break;
201 }
202}
203
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700204static void svc_power(struct svc_function_power *power,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700205 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700206{
207 u8 module_id = power->module_id;
208
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700209 /*
210 * The AP is only allowed to get a Battery Status message, not a Battery
211 * Status Request
212 */
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700213 if (power->power_type != SVC_POWER_BATTERY_STATUS) {
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700214 dev_err(hd->parent, "Received invalid power type %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700215 power->power_type);
216 return;
217 }
218
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700219 /*
220 * As struct struct svc_function_power_battery_status_request is 0 bytes
221 * big, we can just check the union of the whole structure to validate
222 * the size of this message.
223 */
224 if (payload_length != sizeof(struct svc_function_power)) {
225 dev_err(hd->parent,
226 "Illegal size of svc power message %d\n",
227 payload_length);
228 return;
229 }
230
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700231 dev_dbg(hd->parent, "power status for module id %d is %d\n",
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700232 module_id, power->status.status);
233
234 // FIXME - do something with the power information, like update our
235 // battery information...
236}
237
238static void svc_epm(struct svc_function_epm *epm,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700239 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700240{
241 /* What? An AP should not get this message */
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700242 dev_err(hd->parent, "Got an EPM message???\n");
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700243}
244
245static void svc_suspend(struct svc_function_suspend *suspend,
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700246 int payload_length, struct greybus_host_device *hd)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700247{
248 /* What? An AP should not get this message */
Greg Kroah-Hartman772149b2014-09-14 12:27:28 -0700249 dev_err(hd->parent, "Got an suspend message???\n");
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700250}
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700251
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700252static struct svc_msg *convert_ap_message(struct ap_msg *ap_msg,
253 struct greybus_host_device *hd)
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700254{
255 struct svc_msg *svc_msg;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700256 struct svc_msg_header *header;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700257
258 svc_msg = (struct svc_msg *)ap_msg->data;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700259 header = &svc_msg->header;
260
261 /* Validate the message type */
262 if (header->message_type != SVC_MSG_DATA) {
263 dev_err(hd->parent, "message type %d received?\n",
264 header->message_type);
265 return NULL;
266 }
267
268 /*
269 * The validation of the size of the message buffer happens in each
270 * svc_* function, due to the different types of messages, keeping the
271 * logic for each message only in one place.
272 */
Greg Kroah-Hartman43cc32a2014-09-07 13:51:12 -0700273
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700274 return svc_msg;
275}
276
Greg Kroah-Hartmanb57b0622014-09-13 12:18:09 -0700277static void ap_process_event(struct work_struct *work)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700278{
279 struct svc_msg *svc_msg;
280 struct greybus_host_device *hd;
Greg Kroah-Hartmanb57b0622014-09-13 12:18:09 -0700281 struct ap_msg *ap_msg;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700282 int payload_length;
Greg Kroah-Hartmanb57b0622014-09-13 12:18:09 -0700283
284 ap_msg = container_of(work, struct ap_msg, event);
285 hd = ap_msg->hd;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700286
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700287 /* Turn the "raw" data into a real message */
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700288 svc_msg = convert_ap_message(ap_msg, hd);
289 if (!svc_msg)
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700290 return;
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700291
292 payload_length = le16_to_cpu(svc_msg->header.payload_length);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700293
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700294 /* Look at the message to figure out what to do with it */
Matt Porter710ecb02014-09-18 15:25:41 -0400295 switch (svc_msg->header.function_id) {
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700296 case SVC_FUNCTION_HANDSHAKE:
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700297 svc_handshake(&svc_msg->handshake, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700298 break;
299 case SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT:
Greg Kroah-Hartman00c52e42014-09-21 18:19:54 -0700300 svc_management(&svc_msg->management, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700301 break;
302 case SVC_FUNCTION_HOTPLUG:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700303 svc_hotplug(&svc_msg->hotplug, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700304 break;
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700305 case SVC_FUNCTION_POWER:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700306 svc_power(&svc_msg->power, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700307 break;
308 case SVC_FUNCTION_EPM:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700309 svc_epm(&svc_msg->epm, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700310 break;
311 case SVC_FUNCTION_SUSPEND:
Greg Kroah-Hartmand0cfd102014-09-21 19:10:39 -0700312 svc_suspend(&svc_msg->suspend, payload_length, hd);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700313 break;
314 default:
Matt Porter710ecb02014-09-18 15:25:41 -0400315 dev_err(hd->parent, "received invalid SVC function ID %d\n",
316 svc_msg->header.function_id);
Greg Kroah-Hartman8c53e072014-09-12 20:47:11 -0700317 }
318
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700319 /* clean the message up */
320 kfree(ap_msg->data);
321 kfree(ap_msg);
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700322}
323
Alex Elder51c75fd2014-09-26 20:55:35 -0500324int greybus_svc_in(struct greybus_host_device *hd, u8 *data, int size)
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700325{
326 struct ap_msg *ap_msg;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700327
328 /*
329 * Totally naive copy the message into a new structure that we slowly
330 * create and add it to the list. Let's get this working, the odds of
331 * this being any "slow path" for AP messages is really low at this
332 * point in time, but you never know, so this comment is here to point
333 * out that maybe we should use a slab allocator, or even just not copy
334 * the data, but use it directly and force the urbs to be "new" each
335 * time.
336 */
337
338 /* Note - this can, and will, be called in interrupt context. */
339 ap_msg = kmalloc(sizeof(*ap_msg), GFP_ATOMIC);
340 if (!ap_msg)
341 return -ENOMEM;
342 ap_msg->data = kmalloc(size, GFP_ATOMIC);
343 if (!ap_msg->data) {
344 kfree(ap_msg);
345 return -ENOMEM;
346 }
347 memcpy(ap_msg->data, data, size);
348 ap_msg->size = size;
Greg Kroah-Hartman68f1fc42014-09-07 13:12:11 -0700349 ap_msg->hd = hd;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700350
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700351 INIT_WORK(&ap_msg->event, ap_process_event);
352 queue_work(ap_workqueue, &ap_msg->event);
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700353
354 return 0;
355}
Alex Elder0db32a62014-09-24 05:16:14 -0500356EXPORT_SYMBOL_GPL(greybus_svc_in);
Greg Kroah-Hartmand94a44a2014-09-01 14:39:34 -0700357
Greg Kroah-Hartman45f36782014-09-14 11:40:35 -0700358int gb_ap_init(void)
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700359{
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700360 ap_workqueue = alloc_workqueue("greybus_ap", 0, 1);
361 if (!ap_workqueue)
362 return -ENOMEM;
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700363
364 return 0;
365}
366
Greg Kroah-Hartman45f36782014-09-14 11:40:35 -0700367void gb_ap_exit(void)
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700368{
Greg Kroah-Hartman88929c52014-09-13 11:35:02 -0700369 destroy_workqueue(ap_workqueue);
Greg Kroah-Hartmande536e32014-08-31 16:17:04 -0700370}
371
372