greybus: initialize all fields in an SVC handshake message
[cascardo/linux.git] / drivers / staging / greybus / ap.c
1 /*
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>
17 #include <linux/workqueue.h>
18 #include <linux/device.h>
19 #include "svc_msg.h"
20 #include "greybus_manifest.h"
21 #include "greybus.h"
22
23 struct ap_msg {
24         u8 *data;
25         size_t size;
26         struct greybus_host_device *hd;
27         struct work_struct event;
28 };
29
30 static struct workqueue_struct *ap_workqueue;
31
32 static struct svc_msg *svc_msg_alloc(enum svc_function_id id)
33 {
34         struct svc_msg *svc_msg;
35
36         svc_msg = kzalloc((sizeof *svc_msg), GFP_KERNEL);
37         if (!svc_msg)
38                 return NULL;
39
40         // FIXME - verify we are only sending function IDs we should be
41         svc_msg->header.function_id = id;
42         return svc_msg;
43 }
44
45 static void svc_msg_free(struct svc_msg *svc_msg)
46 {
47         kfree(svc_msg);
48 }
49
50 static 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?
56         retval = hd->driver->send_svc_msg(svc_msg, hd);
57
58         svc_msg_free(svc_msg);
59         return retval;
60 }
61
62
63 static void svc_handshake(struct svc_function_handshake *handshake,
64                           int payload_length, struct greybus_host_device *hd)
65 {
66         struct svc_msg *svc_msg;
67
68         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
75         /* 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 */
84         if (handshake->handshake_type != SVC_HANDSHAKE_SVC_HELLO) {
85                 /* we don't know what to do with this, log it and return */
86                 dev_dbg(hd->parent, "received invalid handshake type %d\n",
87                         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
96         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;
102         svc_msg->handshake.handshake_type = SVC_HANDSHAKE_AP_HELLO;
103         svc_msg_send(svc_msg, hd);
104 }
105
106 static void svc_management(struct svc_function_unipro_management *management,
107                            int payload_length, struct greybus_host_device *hd)
108 {
109         if (payload_length != sizeof(struct svc_function_unipro_management)) {
110                 dev_err(hd->parent,
111                         "Illegal size of svc management message %d\n",
112                         payload_length);
113                 return;
114         }
115
116         /* What?  An AP should not get this message */
117         dev_err(hd->parent, "Got an svc management message???\n");
118 }
119
120 static void svc_hotplug(struct svc_function_hotplug *hotplug,
121                         int payload_length, struct greybus_host_device *hd)
122 {
123         u8 module_id = hotplug->module_id;
124
125         switch (hotplug->hotplug_event) {
126         case SVC_HOTPLUG_EVENT:
127                 /* Add a new module to the system */
128                 if (payload_length < 0x03) {
129                         /* Hotplug message is at lest 3 bytes big */
130                         dev_err(hd->parent,
131                                 "Illegal size of svc hotplug message %d\n",
132                                 payload_length);
133                         return;
134                 }
135                 dev_dbg(hd->parent, "module id %d added\n", module_id);
136                 gb_add_module(hd, module_id, hotplug->data,
137                               payload_length - 0x02);
138                 break;
139
140         case SVC_HOTUNPLUG_EVENT:
141                 /* Remove a module from the system */
142                 if (payload_length != 0x02) {
143                         /* Hotunplug message is only 2 bytes big */
144                         dev_err(hd->parent,
145                                 "Illegal size of svc hotunplug message %d\n",
146                                 payload_length);
147                         return;
148                 }
149                 dev_dbg(hd->parent, "module id %d removed\n", module_id);
150                 gb_remove_module(hd, module_id);
151                 break;
152
153         default:
154                 dev_err(hd->parent,
155                         "Received invalid hotplug message type %d\n",
156                         hotplug->hotplug_event);
157                 break;
158         }
159 }
160
161 static void svc_ddb(struct svc_function_ddb *ddb,
162                     int payload_length, struct greybus_host_device *hd)
163 {
164         /*
165          * Need to properly validate payload_length once we start
166          * to handle ddb messages, but for now, we don't, so no need to check
167          * anything.
168          */
169
170         /* What?  An AP should not get this message */
171         dev_err(hd->parent, "Got an svc DDB message???\n");
172 }
173
174 static void svc_power(struct svc_function_power *power,
175                       int payload_length, struct greybus_host_device *hd)
176 {
177         u8 module_id = power->module_id;
178
179         /*
180          * The AP is only allowed to get a Battery Status message, not a Battery
181          * Status Request
182          */
183         if (power->power_type != SVC_POWER_BATTERY_STATUS) {
184                 dev_err(hd->parent, "Received invalid power type %d\n",
185                         power->power_type);
186                 return;
187         }
188
189         /*
190          * As struct struct svc_function_power_battery_status_request is 0 bytes
191          * big, we can just check the union of the whole structure to validate
192          * the size of this message.
193          */
194         if (payload_length != sizeof(struct svc_function_power)) {
195                 dev_err(hd->parent,
196                         "Illegal size of svc power message %d\n",
197                         payload_length);
198                 return;
199         }
200
201         dev_dbg(hd->parent, "power status for module id %d is %d\n",
202                 module_id, power->status.status);
203
204         // FIXME - do something with the power information, like update our
205         // battery information...
206 }
207
208 static void svc_epm(struct svc_function_epm *epm,
209                     int payload_length, struct greybus_host_device *hd)
210 {
211         /* What?  An AP should not get this message */
212         dev_err(hd->parent, "Got an EPM message???\n");
213 }
214
215 static void svc_suspend(struct svc_function_suspend *suspend,
216                         int payload_length, struct greybus_host_device *hd)
217 {
218         /* What?  An AP should not get this message */
219         dev_err(hd->parent, "Got an suspend message???\n");
220 }
221
222 static struct svc_msg *convert_ap_message(struct ap_msg *ap_msg,
223                                           struct greybus_host_device *hd)
224 {
225         struct svc_msg *svc_msg;
226         struct svc_msg_header *header;
227
228         svc_msg = (struct svc_msg *)ap_msg->data;
229         header = &svc_msg->header;
230
231         /* Validate the message type */
232         if (header->message_type != SVC_MSG_DATA) {
233                 dev_err(hd->parent, "message type %d received?\n",
234                         header->message_type);
235                 return NULL;
236         }
237
238         /*
239          * The validation of the size of the message buffer happens in each
240          * svc_* function, due to the different types of messages, keeping the
241          * logic for each message only in one place.
242          */
243
244         return svc_msg;
245 }
246
247 static void ap_process_event(struct work_struct *work)
248 {
249         struct svc_msg *svc_msg;
250         struct greybus_host_device *hd;
251         struct ap_msg *ap_msg;
252         int payload_length;
253
254         ap_msg = container_of(work, struct ap_msg, event);
255         hd = ap_msg->hd;
256
257         /* Turn the "raw" data into a real message */
258         svc_msg = convert_ap_message(ap_msg, hd);
259         if (!svc_msg)
260                 return;
261
262         payload_length = le16_to_cpu(svc_msg->header.payload_length);
263
264         /* Look at the message to figure out what to do with it */
265         switch (svc_msg->header.function_id) {
266         case SVC_FUNCTION_HANDSHAKE:
267                 svc_handshake(&svc_msg->handshake, payload_length, hd);
268                 break;
269         case SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT:
270                 svc_management(&svc_msg->management, payload_length, hd);
271                 break;
272         case SVC_FUNCTION_HOTPLUG:
273                 svc_hotplug(&svc_msg->hotplug, payload_length, hd);
274                 break;
275         case SVC_FUNCTION_DDB:
276                 svc_ddb(&svc_msg->ddb, payload_length, hd);
277                 break;
278         case SVC_FUNCTION_POWER:
279                 svc_power(&svc_msg->power, payload_length, hd);
280                 break;
281         case SVC_FUNCTION_EPM:
282                 svc_epm(&svc_msg->epm, payload_length, hd);
283                 break;
284         case SVC_FUNCTION_SUSPEND:
285                 svc_suspend(&svc_msg->suspend, payload_length, hd);
286                 break;
287         default:
288                 dev_err(hd->parent, "received invalid SVC function ID %d\n",
289                         svc_msg->header.function_id);
290         }
291
292         /* clean the message up */
293         kfree(ap_msg->data);
294         kfree(ap_msg);
295 }
296
297 int gb_new_ap_msg(u8 *data, int size, struct greybus_host_device *hd)
298 {
299         struct ap_msg *ap_msg;
300
301         /*
302          * Totally naive copy the message into a new structure that we slowly
303          * create and add it to the list.  Let's get this working, the odds of
304          * this being any "slow path" for AP messages is really low at this
305          * point in time, but you never know, so this comment is here to point
306          * out that maybe we should use a slab allocator, or even just not copy
307          * the data, but use it directly and force the urbs to be "new" each
308          * time.
309          */
310
311         /* Note - this can, and will, be called in interrupt context. */
312         ap_msg = kmalloc(sizeof(*ap_msg), GFP_ATOMIC);
313         if (!ap_msg)
314                 return -ENOMEM;
315         ap_msg->data = kmalloc(size, GFP_ATOMIC);
316         if (!ap_msg->data) {
317                 kfree(ap_msg);
318                 return -ENOMEM;
319         }
320         memcpy(ap_msg->data, data, size);
321         ap_msg->size = size;
322         ap_msg->hd = hd;
323
324         INIT_WORK(&ap_msg->event, ap_process_event);
325         queue_work(ap_workqueue, &ap_msg->event);
326
327         return 0;
328 }
329 EXPORT_SYMBOL_GPL(gb_new_ap_msg);
330
331 int gb_ap_init(void)
332 {
333         ap_workqueue = alloc_workqueue("greybus_ap", 0, 1);
334         if (!ap_workqueue)
335                 return -ENOMEM;
336
337         return 0;
338 }
339
340 void gb_ap_exit(void)
341 {
342         destroy_workqueue(ap_workqueue);
343 }
344
345