summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus/ap.c
blob: c2c5aa654e7ec31bbc1bd65b1f3a4fcd591448de (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/*
 * Greybus "AP" message loop handling
 *
 * Copyright 2014 Google Inc.
 *
 * Released under the GPLv2 only.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/types.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/device.h>
#include "svc_msg.h"
#include "greybus_manifest.h"
#include "greybus.h"

struct ap_msg {
	u8 *data;
	size_t size;
	struct greybus_host_device *hd;
	struct work_struct event;
};

static struct workqueue_struct *ap_workqueue;

static struct svc_msg *svc_msg_alloc(enum svc_function_id id)
{
	struct svc_msg *svc_msg;

	svc_msg = kzalloc((sizeof *svc_msg), GFP_KERNEL);
	if (!svc_msg)
		return NULL;

	// FIXME - verify we are only sending function IDs we should be
	svc_msg->header.function_id = id;
	return svc_msg;
}

static void svc_msg_free(struct svc_msg *svc_msg)
{
	kfree(svc_msg);
}

static int svc_msg_send(struct svc_msg *svc_msg, struct greybus_host_device *hd)
{
	int retval;

	// FIXME - Do we need to do more than just pass it to the hd and then
	// free it?
	retval = hd->driver->send_svc_msg(svc_msg, hd);

	svc_msg_free(svc_msg);
	return retval;
}


static void svc_handshake(struct svc_function_handshake *handshake,
			  struct greybus_host_device *hd)
{
	struct svc_msg *svc_msg;

	/* A new SVC communication channel, let's verify it was for us */
	if (handshake->handshake_type != SVC_HANDSHAKE_SVC_HELLO) {
		/* we don't know what to do with this, log it and return */
		dev_dbg(hd->parent, "received invalid handshake type %d\n",
			handshake->handshake_type);
		return;
	}

	/* Send back a AP_HELLO message */
	svc_msg = svc_msg_alloc(SVC_FUNCTION_HANDSHAKE);
	if (!svc_msg)
		return;

	svc_msg->handshake.handshake_type = SVC_HANDSHAKE_AP_HELLO;
	svc_msg_send(svc_msg, hd);
}

static void svc_management(struct svc_function_unipro_management *management,
			   struct greybus_host_device *hd)
{
	/* What?  An AP should not get this message */
	dev_err(hd->parent, "Got an svc management message???\n");
}

static void svc_hotplug(struct svc_function_hotplug *hotplug,
			struct greybus_host_device *hd)
{
	u8 module_id = hotplug->module_id;

	switch (hotplug->hotplug_event) {
	case SVC_HOTPLUG_EVENT:
		dev_dbg(hd->parent, "module id %d added\n", module_id);
		// FIXME - add the module to the system
		break;

	case SVC_HOTUNPLUG_EVENT:
		dev_dbg(hd->parent, "module id %d removed\n", module_id);
		// FIXME - remove the module from the system
		break;

	default:
		dev_err(hd->parent,
			"received invalid hotplug message type %d\n",
			hotplug->hotplug_event);
		break;
	}
}

static void svc_ddb(struct svc_function_ddb *ddb,
		    struct greybus_host_device *hd)
{
	/* What?  An AP should not get this message */
	dev_err(hd->parent, "Got an svc DDB message???\n");
}

static void svc_power(struct svc_function_power *power,
		      struct greybus_host_device *hd)
{
	u8 module_id = power->module_id;

	if (power->power_type != SVC_POWER_BATTERY_STATUS) {
		dev_err(hd->parent, "received invalid power type %d\n",
			power->power_type);
		return;
	}

	dev_dbg(hd->parent, "power status for module id %d is %d\n",
		module_id, power->status.status);

	// FIXME - do something with the power information, like update our
	// battery information...
}

static void svc_epm(struct svc_function_epm *epm,
		    struct greybus_host_device *hd)
{
	/* What?  An AP should not get this message */
	dev_err(hd->parent, "Got an EPM message???\n");
}

static void svc_suspend(struct svc_function_suspend *suspend,
			struct greybus_host_device *hd)
{
	/* What?  An AP should not get this message */
	dev_err(hd->parent, "Got an suspend message???\n");
}

static struct svc_msg *convert_ap_message(struct ap_msg *ap_msg)
{
	struct svc_msg *svc_msg;

	// FIXME - validate message, right now we are trusting the size and data
	// from the AP, what could go wrong?  :)
	// for now, just cast the pointer and run away...

	svc_msg = (struct svc_msg *)ap_msg->data;

	/* Verify the version is something we can handle with this code */
	if ((svc_msg->header.version_major != GREYBUS_VERSION_MAJOR) &&
	    (svc_msg->header.version_minor != GREYBUS_VERSION_MINOR))
		return NULL;

	return svc_msg;
}

static void ap_process_event(struct work_struct *work)
{
	struct svc_msg *svc_msg;
	struct greybus_host_device *hd;
	struct ap_msg *ap_msg;

	ap_msg = container_of(work, struct ap_msg, event);
	hd = ap_msg->hd;

	/* Turn the "raw" data into a real message */
	svc_msg = convert_ap_message(ap_msg);
	if (!svc_msg) {
		// FIXME log an error???
		return;
	}

	/* Look at the message to figure out what to do with it */
	switch (svc_msg->header.function_id) {
	case SVC_FUNCTION_HANDSHAKE:
		svc_handshake(&svc_msg->handshake, hd);
		break;
	case SVC_FUNCTION_UNIPRO_NETWORK_MANAGEMENT:
		svc_management(&svc_msg->management, hd);
		break;
	case SVC_FUNCTION_HOTPLUG:
		svc_hotplug(&svc_msg->hotplug, hd);
		break;
	case SVC_FUNCTION_DDB:
		svc_ddb(&svc_msg->ddb, hd);
		break;
	case SVC_FUNCTION_POWER:
		svc_power(&svc_msg->power, hd);
		break;
	case SVC_FUNCTION_EPM:
		svc_epm(&svc_msg->epm, hd);
		break;
	case SVC_FUNCTION_SUSPEND:
		svc_suspend(&svc_msg->suspend, hd);
		break;
	default:
		dev_err(hd->parent, "received invalid SVC function ID %d\n",
			svc_msg->header.function_id);
	}

	/* clean the message up */
	kfree(ap_msg->data);
	kfree(ap_msg);
}

int gb_new_ap_msg(u8 *data, int size, struct greybus_host_device *hd)
{
	struct ap_msg *ap_msg;

	/*
	 * Totally naive copy the message into a new structure that we slowly
	 * create and add it to the list.  Let's get this working, the odds of
	 * this being any "slow path" for AP messages is really low at this
	 * point in time, but you never know, so this comment is here to point
	 * out that maybe we should use a slab allocator, or even just not copy
	 * the data, but use it directly and force the urbs to be "new" each
	 * time.
	 */

	/* Note - this can, and will, be called in interrupt context. */
	ap_msg = kmalloc(sizeof(*ap_msg), GFP_ATOMIC);
	if (!ap_msg)
		return -ENOMEM;
	ap_msg->data = kmalloc(size, GFP_ATOMIC);
	if (!ap_msg->data) {
		kfree(ap_msg);
		return -ENOMEM;
	}
	memcpy(ap_msg->data, data, size);
	ap_msg->size = size;
	ap_msg->hd = hd;

	INIT_WORK(&ap_msg->event, ap_process_event);
	queue_work(ap_workqueue, &ap_msg->event);

	return 0;
}
EXPORT_SYMBOL_GPL(gb_new_ap_msg);

int gb_ap_init(void)
{
	ap_workqueue = alloc_workqueue("greybus_ap", 0, 1);
	if (!ap_workqueue)
		return -ENOMEM;

	return 0;
}

void gb_ap_exit(void)
{
	destroy_workqueue(ap_workqueue);
}