summaryrefslogtreecommitdiffstats
path: root/drivers/staging/greybus/svc.c
blob: e39eddbbcb9eb429f5ed7e39f85ff8aa91ff6986 (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
/*
 * SVC Greybus driver.
 *
 * Copyright 2015 Google Inc.
 * Copyright 2015 Linaro Ltd.
 *
 * Released under the GPLv2 only.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>

#include "greybus.h"
#include "greybus_protocols.h"

struct gb_svc {
	struct gb_connection	*connection;
	u8			version_major;
	u8			version_minor;
};

/* Define get_version() routine */
define_get_version(gb_svc, SVC);

static int intf_device_id_operation(struct gb_svc *svc,
				u8 intf_id, u8 device_id)
{
	struct gb_svc_intf_device_id_request request;

	request.intf_id = intf_id;
	request.device_id = device_id;

	return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_DEVICE_ID,
				 &request, sizeof(request), NULL, 0);
}

static int intf_reset_operation(struct gb_svc *svc, u8 intf_id)
{
	struct gb_svc_intf_reset_request request;

	request.intf_id = intf_id;

	return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_RESET,
				 &request, sizeof(request), NULL, 0);
}

static int connection_create_operation(struct gb_svc *svc,
				u8 intf1_id, u16 cport1_id,
				u8 intf2_id, u16 cport2_id)
{
	struct gb_svc_conn_create_request request;

	request.intf1_id = intf1_id;
	request.cport1_id = cport1_id;
	request.intf2_id = intf2_id;
	request.cport2_id = cport2_id;

	return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_CREATE,
				 &request, sizeof(request), NULL, 0);
}

static int connection_destroy_operation(struct gb_svc *svc,
				u8 intf1_id, u16 cport1_id,
				u8 intf2_id, u16 cport2_id)
{
	struct gb_svc_conn_destroy_request request;

	request.intf1_id = intf1_id;
	request.cport1_id = cport1_id;
	request.intf2_id = intf2_id;
	request.cport2_id = cport2_id;

	return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_DESTROY,
				 &request, sizeof(request), NULL, 0);
}

int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id)
{
	return intf_device_id_operation(svc, intf_id, device_id);
}
EXPORT_SYMBOL_GPL(gb_svc_intf_device_id);

int gb_svc_intf_reset(struct gb_svc *svc, u8 intf_id)
{
	return intf_reset_operation(svc, intf_id);
}
EXPORT_SYMBOL_GPL(gb_svc_intf_reset);

int gb_svc_connection_create(struct gb_svc *svc,
				u8 intf1_id, u16 cport1_id,
				u8 intf2_id, u16 cport2_id)
{
	return connection_create_operation(svc, intf1_id, cport1_id,
						intf2_id, cport2_id);
}
EXPORT_SYMBOL_GPL(gb_svc_connection_create);

int gb_svc_connection_destroy(struct gb_svc *svc,
				u8 intf1_id, u16 cport1_id,
				u8 intf2_id, u16 cport2_id)
{
	return connection_destroy_operation(svc, intf1_id, cport1_id,
						intf2_id, cport2_id);
}
EXPORT_SYMBOL_GPL(gb_svc_connection_destroy);

static int gb_svc_intf_hotplug_recv(struct gb_operation *op)
{
	struct gb_message *request = op->request;
	struct gb_svc_intf_hotplug_request *hotplug;
	u8 intf_id;
	u32 unipro_mfg_id;
	u32 unipro_prod_id;
	u32 ara_vend_id;
	u32 ara_prod_id;

	if (request->payload_size < sizeof(*hotplug)) {
		dev_err(&op->connection->dev,
			"short hotplug request received\n");
		return -EINVAL;
	}
	hotplug = request->payload;

	/*
	 * Grab the information we need.
	 *
	 * XXX I'd really like to acknowledge receipt, and then
	 * XXX continue processing the request.  There's no need
	 * XXX for the SVC to wait.  In fact, it might be best to
	 * XXX have the SVC get acknowledgement before we proceed.
	 * */
	intf_id = hotplug->intf_id;
	unipro_mfg_id = hotplug->data.unipro_mfg_id;
	unipro_prod_id = hotplug->data.unipro_prod_id;
	ara_vend_id = hotplug->data.ara_vend_id;
	ara_prod_id = hotplug->data.ara_prod_id;

	/* FIXME Set up the interface here; may required firmware download */

	return 0;
}

static int gb_svc_intf_hot_unplug_recv(struct gb_operation *op)
{
	struct gb_message *request = op->request;
	struct gb_svc_intf_hot_unplug_request *hot_unplug;
	u8 intf_id;

	if (request->payload_size < sizeof(*hot_unplug)) {
		dev_err(&op->connection->dev,
			"short hot unplug request received\n");
		return -EINVAL;
	}
	hot_unplug = request->payload;

	intf_id = hot_unplug->intf_id;

	/* FIXME Tear down the interface here */

	return 0;

}

static int gb_svc_intf_reset_recv(struct gb_operation *op)
{
	struct gb_message *request = op->request;
	struct gb_svc_intf_reset_request *reset;
	u8 intf_id;

	if (request->payload_size < sizeof(*reset)) {
		dev_err(&op->connection->dev,
			"short reset request received\n");
		return -EINVAL;
	}
	reset = request->payload;

	intf_id = reset->intf_id;

	/* FIXME Reset the interface here */

	return 0;
}

static int gb_svc_request_recv(u8 type, struct gb_operation *op)
{
	switch (type) {
	case GB_SVC_TYPE_INTF_HOTPLUG:
		return gb_svc_intf_hotplug_recv(op);
	case GB_SVC_TYPE_INTF_HOT_UNPLUG:
		return gb_svc_intf_hot_unplug_recv(op);
	case GB_SVC_TYPE_INTF_RESET:
		return gb_svc_intf_reset_recv(op);
	default:
		dev_err(&op->connection->dev,
			"unsupported request: %hhu\n", type);
		return -EINVAL;
	}
}

/*
 * Do initial setup of the SVC.
 */
static int gb_svc_device_setup(struct gb_svc *gb_svc)
{
	/* First thing we need to do is check the version */
	return get_version(gb_svc);
}

static int gb_svc_connection_init(struct gb_connection *connection)
{
	struct gb_svc *svc;
	int ret;

	svc = kzalloc(sizeof(*svc), GFP_KERNEL);
	if (!svc)
		return -ENOMEM;

	svc->connection = connection;
	connection->private = svc;
	ret = gb_svc_device_setup(svc);
	if (ret)
		kfree(svc);

	return ret;
}

static void gb_svc_connection_exit(struct gb_connection *connection)
{
	struct gb_svc *svc = connection->private;

	if (!svc)
		return;

	kfree(svc);
}

static struct gb_protocol svc_protocol = {
	.name			= "svc",
	.id			= GREYBUS_PROTOCOL_SVC,
	.major			= 0,
	.minor			= 1,
	.connection_init	= gb_svc_connection_init,
	.connection_exit	= gb_svc_connection_exit,
	.request_recv		= gb_svc_request_recv,
};

int gb_svc_protocol_init(void)
{
	return gb_protocol_register(&svc_protocol);
}

void gb_svc_protocol_exit(void)
{
	gb_protocol_deregister(&svc_protocol);
}
OpenPOWER on IntegriCloud