summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus/authentication.c
blob: 297e69f011c7e1386c736df84f1b302bb3d55d7c (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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
// SPDX-License-Identifier: GPL-2.0
/*
 * Greybus Component Authentication Protocol (CAP) Driver.
 *
 * Copyright 2016 Google Inc.
 * Copyright 2016 Linaro Ltd.
 */

#include <linux/greybus.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include "greybus_authentication.h"
#include "firmware.h"

#define CAP_TIMEOUT_MS		1000

/*
 * Number of minor devices this driver supports.
 * There will be exactly one required per Interface.
 */
#define NUM_MINORS		U8_MAX

struct gb_cap {
	struct device		*parent;
	struct gb_connection	*connection;
	struct kref		kref;
	struct list_head	node;
	bool			disabled; /* connection getting disabled */

	struct mutex		mutex;
	struct cdev		cdev;
	struct device		*class_device;
	dev_t			dev_num;
};

static struct class *cap_class;
static dev_t cap_dev_num;
static DEFINE_IDA(cap_minors_map);
static LIST_HEAD(cap_list);
static DEFINE_MUTEX(list_mutex);

static void cap_kref_release(struct kref *kref)
{
	struct gb_cap *cap = container_of(kref, struct gb_cap, kref);

	kfree(cap);
}

/*
 * All users of cap take a reference (from within list_mutex lock), before
 * they get a pointer to play with. And the structure will be freed only after
 * the last user has put the reference to it.
 */
static void put_cap(struct gb_cap *cap)
{
	kref_put(&cap->kref, cap_kref_release);
}

/* Caller must call put_cap() after using struct gb_cap */
static struct gb_cap *get_cap(struct cdev *cdev)
{
	struct gb_cap *cap;

	mutex_lock(&list_mutex);

	list_for_each_entry(cap, &cap_list, node) {
		if (&cap->cdev == cdev) {
			kref_get(&cap->kref);
			goto unlock;
		}
	}

	cap = NULL;

unlock:
	mutex_unlock(&list_mutex);

	return cap;
}

static int cap_get_endpoint_uid(struct gb_cap *cap, u8 *euid)
{
	struct gb_connection *connection = cap->connection;
	struct gb_cap_get_endpoint_uid_response response;
	int ret;

	ret = gb_operation_sync(connection, GB_CAP_TYPE_GET_ENDPOINT_UID, NULL,
				0, &response, sizeof(response));
	if (ret) {
		dev_err(cap->parent, "failed to get endpoint uid (%d)\n", ret);
		return ret;
	}

	memcpy(euid, response.uid, sizeof(response.uid));

	return 0;
}

static int cap_get_ims_certificate(struct gb_cap *cap, u32 class, u32 id,
				   u8 *certificate, u32 *size, u8 *result)
{
	struct gb_connection *connection = cap->connection;
	struct gb_cap_get_ims_certificate_request *request;
	struct gb_cap_get_ims_certificate_response *response;
	size_t max_size = gb_operation_get_payload_size_max(connection);
	struct gb_operation *op;
	int ret;

	op = gb_operation_create_flags(connection,
				       GB_CAP_TYPE_GET_IMS_CERTIFICATE,
				       sizeof(*request), max_size,
				       GB_OPERATION_FLAG_SHORT_RESPONSE,
				       GFP_KERNEL);
	if (!op)
		return -ENOMEM;

	request = op->request->payload;
	request->certificate_class = cpu_to_le32(class);
	request->certificate_id = cpu_to_le32(id);

	ret = gb_operation_request_send_sync(op);
	if (ret) {
		dev_err(cap->parent, "failed to get certificate (%d)\n", ret);
		goto done;
	}

	response = op->response->payload;
	*result = response->result_code;
	*size = op->response->payload_size - sizeof(*response);
	memcpy(certificate, response->certificate, *size);

done:
	gb_operation_put(op);
	return ret;
}

static int cap_authenticate(struct gb_cap *cap, u32 auth_type, u8 *uid,
			    u8 *challenge, u8 *result, u8 *auth_response,
			    u32 *signature_size, u8 *signature)
{
	struct gb_connection *connection = cap->connection;
	struct gb_cap_authenticate_request *request;
	struct gb_cap_authenticate_response *response;
	size_t max_size = gb_operation_get_payload_size_max(connection);
	struct gb_operation *op;
	int ret;

	op = gb_operation_create_flags(connection, GB_CAP_TYPE_AUTHENTICATE,
				       sizeof(*request), max_size,
				       GB_OPERATION_FLAG_SHORT_RESPONSE,
				       GFP_KERNEL);
	if (!op)
		return -ENOMEM;

	request = op->request->payload;
	request->auth_type = cpu_to_le32(auth_type);
	memcpy(request->uid, uid, sizeof(request->uid));
	memcpy(request->challenge, challenge, sizeof(request->challenge));

	ret = gb_operation_request_send_sync(op);
	if (ret) {
		dev_err(cap->parent, "failed to authenticate (%d)\n", ret);
		goto done;
	}

	response = op->response->payload;
	*result = response->result_code;
	*signature_size = op->response->payload_size - sizeof(*response);
	memcpy(auth_response, response->response, sizeof(response->response));
	memcpy(signature, response->signature, *signature_size);

done:
	gb_operation_put(op);
	return ret;
}

/* Char device fops */

static int cap_open(struct inode *inode, struct file *file)
{
	struct gb_cap *cap = get_cap(inode->i_cdev);

	/* cap structure can't get freed until file descriptor is closed */
	if (cap) {
		file->private_data = cap;
		return 0;
	}

	return -ENODEV;
}

static int cap_release(struct inode *inode, struct file *file)
{
	struct gb_cap *cap = file->private_data;

	put_cap(cap);
	return 0;
}

static int cap_ioctl(struct gb_cap *cap, unsigned int cmd,
		     void __user *buf)
{
	struct cap_ioc_get_endpoint_uid endpoint_uid;
	struct cap_ioc_get_ims_certificate *ims_cert;
	struct cap_ioc_authenticate *authenticate;
	size_t size;
	int ret;

	switch (cmd) {
	case CAP_IOC_GET_ENDPOINT_UID:
		ret = cap_get_endpoint_uid(cap, endpoint_uid.uid);
		if (ret)
			return ret;

		if (copy_to_user(buf, &endpoint_uid, sizeof(endpoint_uid)))
			return -EFAULT;

		return 0;
	case CAP_IOC_GET_IMS_CERTIFICATE:
		size = sizeof(*ims_cert);
		ims_cert = memdup_user(buf, size);
		if (IS_ERR(ims_cert))
			return PTR_ERR(ims_cert);

		ret = cap_get_ims_certificate(cap, ims_cert->certificate_class,
					      ims_cert->certificate_id,
					      ims_cert->certificate,
					      &ims_cert->cert_size,
					      &ims_cert->result_code);
		if (!ret && copy_to_user(buf, ims_cert, size))
			ret = -EFAULT;
		kfree(ims_cert);

		return ret;
	case CAP_IOC_AUTHENTICATE:
		size = sizeof(*authenticate);
		authenticate = memdup_user(buf, size);
		if (IS_ERR(authenticate))
			return PTR_ERR(authenticate);

		ret = cap_authenticate(cap, authenticate->auth_type,
				       authenticate->uid,
				       authenticate->challenge,
				       &authenticate->result_code,
				       authenticate->response,
				       &authenticate->signature_size,
				       authenticate->signature);
		if (!ret && copy_to_user(buf, authenticate, size))
			ret = -EFAULT;
		kfree(authenticate);

		return ret;
	default:
		return -ENOTTY;
	}
}

static long cap_ioctl_unlocked(struct file *file, unsigned int cmd,
			       unsigned long arg)
{
	struct gb_cap *cap = file->private_data;
	struct gb_bundle *bundle = cap->connection->bundle;
	int ret = -ENODEV;

	/*
	 * Serialize ioctls.
	 *
	 * We don't want the user to do multiple authentication operations in
	 * parallel.
	 *
	 * This is also used to protect ->disabled, which is used to check if
	 * the connection is getting disconnected, so that we don't start any
	 * new operations.
	 */
	mutex_lock(&cap->mutex);
	if (!cap->disabled) {
		ret = gb_pm_runtime_get_sync(bundle);
		if (!ret) {
			ret = cap_ioctl(cap, cmd, (void __user *)arg);
			gb_pm_runtime_put_autosuspend(bundle);
		}
	}
	mutex_unlock(&cap->mutex);

	return ret;
}

static const struct file_operations cap_fops = {
	.owner		= THIS_MODULE,
	.open		= cap_open,
	.release	= cap_release,
	.unlocked_ioctl	= cap_ioctl_unlocked,
};

int gb_cap_connection_init(struct gb_connection *connection)
{
	struct gb_cap *cap;
	int ret, minor;

	if (!connection)
		return 0;

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

	cap->parent = &connection->bundle->dev;
	cap->connection = connection;
	mutex_init(&cap->mutex);
	gb_connection_set_data(connection, cap);
	kref_init(&cap->kref);

	mutex_lock(&list_mutex);
	list_add(&cap->node, &cap_list);
	mutex_unlock(&list_mutex);

	ret = gb_connection_enable(connection);
	if (ret)
		goto err_list_del;

	minor = ida_simple_get(&cap_minors_map, 0, NUM_MINORS, GFP_KERNEL);
	if (minor < 0) {
		ret = minor;
		goto err_connection_disable;
	}

	/* Add a char device to allow userspace to interact with cap */
	cap->dev_num = MKDEV(MAJOR(cap_dev_num), minor);
	cdev_init(&cap->cdev, &cap_fops);

	ret = cdev_add(&cap->cdev, cap->dev_num, 1);
	if (ret)
		goto err_remove_ida;

	/* Add a soft link to the previously added char-dev within the bundle */
	cap->class_device = device_create(cap_class, cap->parent, cap->dev_num,
					  NULL, "gb-authenticate-%d", minor);
	if (IS_ERR(cap->class_device)) {
		ret = PTR_ERR(cap->class_device);
		goto err_del_cdev;
	}

	return 0;

err_del_cdev:
	cdev_del(&cap->cdev);
err_remove_ida:
	ida_simple_remove(&cap_minors_map, minor);
err_connection_disable:
	gb_connection_disable(connection);
err_list_del:
	mutex_lock(&list_mutex);
	list_del(&cap->node);
	mutex_unlock(&list_mutex);

	put_cap(cap);

	return ret;
}

void gb_cap_connection_exit(struct gb_connection *connection)
{
	struct gb_cap *cap;

	if (!connection)
		return;

	cap = gb_connection_get_data(connection);

	device_destroy(cap_class, cap->dev_num);
	cdev_del(&cap->cdev);
	ida_simple_remove(&cap_minors_map, MINOR(cap->dev_num));

	/*
	 * Disallow any new ioctl operations on the char device and wait for
	 * existing ones to finish.
	 */
	mutex_lock(&cap->mutex);
	cap->disabled = true;
	mutex_unlock(&cap->mutex);

	/* All pending greybus operations should have finished by now */
	gb_connection_disable(cap->connection);

	/* Disallow new users to get access to the cap structure */
	mutex_lock(&list_mutex);
	list_del(&cap->node);
	mutex_unlock(&list_mutex);

	/*
	 * All current users of cap would have taken a reference to it by
	 * now, we can drop our reference and wait the last user will get
	 * cap freed.
	 */
	put_cap(cap);
}

int cap_init(void)
{
	int ret;

	cap_class = class_create(THIS_MODULE, "gb_authenticate");
	if (IS_ERR(cap_class))
		return PTR_ERR(cap_class);

	ret = alloc_chrdev_region(&cap_dev_num, 0, NUM_MINORS,
				  "gb_authenticate");
	if (ret)
		goto err_remove_class;

	return 0;

err_remove_class:
	class_destroy(cap_class);
	return ret;
}

void cap_exit(void)
{
	unregister_chrdev_region(cap_dev_num, NUM_MINORS);
	class_destroy(cap_class);
	ida_destroy(&cap_minors_map);
}