summaryrefslogtreecommitdiffstats
path: root/drivers/tee/optee/notif.c
blob: 1970880c796ffe982bca2011519bff93898529b7 (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
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2015-2021, Linaro Limited
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/arm-smccc.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/tee_core.h>
#include "optee_private.h"

struct notif_entry {
	struct list_head link;
	struct completion c;
	u_int key;
};

static bool have_key(struct optee *optee, u_int key)
{
	struct notif_entry *entry;

	list_for_each_entry(entry, &optee->notif.db, link)
		if (entry->key == key)
			return true;

	return false;
}

int optee_notif_wait(struct optee *optee, u_int key, u32 timeout)
{
	unsigned long flags;
	struct notif_entry *entry;
	int rc = 0;

	if (key > optee->notif.max_key)
		return -EINVAL;

	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
	if (!entry)
		return -ENOMEM;
	init_completion(&entry->c);
	entry->key = key;

	spin_lock_irqsave(&optee->notif.lock, flags);

	/*
	 * If the bit is already set it means that the key has already
	 * been posted and we must not wait.
	 */
	if (test_bit(key, optee->notif.bitmap)) {
		clear_bit(key, optee->notif.bitmap);
		goto out;
	}

	/*
	 * Check if someone is already waiting for this key. If there is
	 * it's a programming error.
	 */
	if (have_key(optee, key)) {
		rc = -EBUSY;
		goto out;
	}

	list_add_tail(&entry->link, &optee->notif.db);

	/*
	 * Unlock temporarily and wait for completion.
	 */
	spin_unlock_irqrestore(&optee->notif.lock, flags);
	if (timeout != 0) {
		if (!wait_for_completion_timeout(&entry->c, timeout))
			rc = -ETIMEDOUT;
	} else {
		wait_for_completion(&entry->c);
	}
	spin_lock_irqsave(&optee->notif.lock, flags);

	list_del(&entry->link);
out:
	spin_unlock_irqrestore(&optee->notif.lock, flags);

	kfree(entry);

	return rc;
}

int optee_notif_send(struct optee *optee, u_int key)
{
	unsigned long flags;
	struct notif_entry *entry;

	if (key > optee->notif.max_key)
		return -EINVAL;

	spin_lock_irqsave(&optee->notif.lock, flags);

	list_for_each_entry(entry, &optee->notif.db, link)
		if (entry->key == key) {
			complete(&entry->c);
			goto out;
		}

	/* Only set the bit in case there where nobody waiting */
	set_bit(key, optee->notif.bitmap);
out:
	spin_unlock_irqrestore(&optee->notif.lock, flags);

	return 0;
}

int optee_notif_init(struct optee *optee, u_int max_key)
{
	spin_lock_init(&optee->notif.lock);
	INIT_LIST_HEAD(&optee->notif.db);
	optee->notif.bitmap = bitmap_zalloc(max_key, GFP_KERNEL);
	if (!optee->notif.bitmap)
		return -ENOMEM;

	optee->notif.max_key = max_key;

	return 0;
}

void optee_notif_uninit(struct optee *optee)
{
	bitmap_free(optee->notif.bitmap);
}