summaryrefslogtreecommitdiff
path: root/ipc/kdbus/reply.c
blob: 008dca801627edd2351be459cac63ba853d14d86 (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
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/uio.h>

#include "bus.h"
#include "connection.h"
#include "endpoint.h"
#include "message.h"
#include "metadata.h"
#include "names.h"
#include "domain.h"
#include "item.h"
#include "notify.h"
#include "policy.h"
#include "reply.h"
#include "util.h"

/**
 * kdbus_reply_new() - Allocate and set up a new kdbus_reply object
 * @reply_src:		The connection a reply is expected from
 * @reply_dst:		The connection this reply object belongs to
 * @msg:		Message associated with the reply
 * @name_entry:		Name entry used to send the message
 * @sync:		Whether or not to make this reply synchronous
 *
 * Allocate and fill a new kdbus_reply object.
 *
 * Return: New kdbus_conn object on success, ERR_PTR on error.
 */
struct kdbus_reply *kdbus_reply_new(struct kdbus_conn *reply_src,
				    struct kdbus_conn *reply_dst,
				    const struct kdbus_msg *msg,
				    struct kdbus_name_entry *name_entry,
				    bool sync)
{
	struct kdbus_reply *r;
	int ret = 0;

	if (atomic_inc_return(&reply_dst->request_count) >
	    KDBUS_CONN_MAX_REQUESTS_PENDING) {
		ret = -EMLINK;
		goto exit_dec_request_count;
	}

	r = kzalloc(sizeof(*r), GFP_KERNEL);
	if (!r) {
		ret = -ENOMEM;
		goto exit_dec_request_count;
	}

	kref_init(&r->kref);
	INIT_LIST_HEAD(&r->entry);
	r->reply_src = kdbus_conn_ref(reply_src);
	r->reply_dst = kdbus_conn_ref(reply_dst);
	r->cookie = msg->cookie;
	r->name_id = name_entry ? name_entry->name_id : 0;
	r->deadline_ns = msg->timeout_ns;

	if (sync) {
		r->sync = true;
		r->waiting = true;
	}

exit_dec_request_count:
	if (ret < 0) {
		atomic_dec(&reply_dst->request_count);
		return ERR_PTR(ret);
	}

	return r;
}

static void __kdbus_reply_free(struct kref *kref)
{
	struct kdbus_reply *reply =
		container_of(kref, struct kdbus_reply, kref);

	atomic_dec(&reply->reply_dst->request_count);
	kdbus_conn_unref(reply->reply_src);
	kdbus_conn_unref(reply->reply_dst);
	kfree(reply);
}

/**
 * kdbus_reply_ref() - Increase reference on kdbus_reply
 * @r:		The reply, may be %NULL
 *
 * Return: The reply object with an extra reference
 */
struct kdbus_reply *kdbus_reply_ref(struct kdbus_reply *r)
{
	if (r)
		kref_get(&r->kref);
	return r;
}

/**
 * kdbus_reply_unref() - Decrease reference on kdbus_reply
 * @r:		The reply, may be %NULL
 *
 * Return: NULL
 */
struct kdbus_reply *kdbus_reply_unref(struct kdbus_reply *r)
{
	if (r)
		kref_put(&r->kref, __kdbus_reply_free);
	return NULL;
}

/**
 * kdbus_reply_link() - Link reply object into target connection
 * @r:		Reply to link
 */
void kdbus_reply_link(struct kdbus_reply *r)
{
	if (WARN_ON(!list_empty(&r->entry)))
		return;

	list_add(&r->entry, &r->reply_dst->reply_list);
	kdbus_reply_ref(r);
}

/**
 * kdbus_reply_unlink() - Unlink reply object from target connection
 * @r:		Reply to unlink
 */
void kdbus_reply_unlink(struct kdbus_reply *r)
{
	if (!list_empty(&r->entry)) {
		list_del_init(&r->entry);
		kdbus_reply_unref(r);
	}
}

/**
 * kdbus_sync_reply_wakeup() - Wake a synchronously blocking reply
 * @reply:	The reply object
 * @err:	Error code to set on the remote side
 *
 * Remove the synchronous reply object from its connection reply_list, and
 * wake up remote peer (method origin) with the appropriate synchronous reply
 * code.
 */
void kdbus_sync_reply_wakeup(struct kdbus_reply *reply, int err)
{
	if (WARN_ON(!reply->sync))
		return;

	reply->waiting = false;
	reply->err = err;
	wake_up_interruptible(&reply->reply_dst->wait);
}

/**
 * kdbus_reply_find() - Find the corresponding reply object
 * @replying:	The replying connection or NULL
 * @reply_dst:	The connection the reply will be sent to
 *		(method origin)
 * @cookie:	The cookie of the requesting message
 *
 * Lookup a reply object that should be sent as a reply by
 * @replying to @reply_dst with the given cookie.
 *
 * Callers must take the @reply_dst lock.
 *
 * Return: the corresponding reply object or NULL if not found
 */
struct kdbus_reply *kdbus_reply_find(struct kdbus_conn *replying,
				     struct kdbus_conn *reply_dst,
				     u64 cookie)
{
	struct kdbus_reply *r, *reply = NULL;

	list_for_each_entry(r, &reply_dst->reply_list, entry) {
		if (r->cookie == cookie &&
		    (!replying || r->reply_src == replying)) {
			reply = r;
			break;
		}
	}

	return reply;
}

/**
 * kdbus_reply_list_scan_work() - Worker callback to scan the replies of a
 *				  connection for exceeded timeouts
 * @work:		Work struct of the connection to scan
 *
 * Walk the list of replies stored with a connection and look for entries
 * that have exceeded their timeout. If such an entry is found, a timeout
 * notification is sent to the waiting peer, and the reply is removed from
 * the list.
 *
 * The work is rescheduled to the nearest timeout found during the list
 * iteration.
 */
void kdbus_reply_list_scan_work(struct work_struct *work)
{
	struct kdbus_conn *conn =
		container_of(work, struct kdbus_conn, work.work);
	struct kdbus_reply *reply, *reply_tmp;
	u64 deadline = ~0ULL;
	u64 now;

	now = ktime_get_ns();

	mutex_lock(&conn->lock);
	if (!kdbus_conn_active(conn)) {
		mutex_unlock(&conn->lock);
		return;
	}

	list_for_each_entry_safe(reply, reply_tmp, &conn->reply_list, entry) {
		/*
		 * If the reply block is waiting for synchronous I/O,
		 * the timeout is handled by wait_event_*_timeout(),
		 * so we don't have to care for it here.
		 */
		if (reply->sync && !reply->interrupted)
			continue;

		WARN_ON(reply->reply_dst != conn);

		if (reply->deadline_ns > now) {
			/* remember next timeout */
			if (deadline > reply->deadline_ns)
				deadline = reply->deadline_ns;

			continue;
		}

		/*
		 * A zero deadline means the connection died, was
		 * cleaned up already and the notification was sent.
		 * Don't send notifications for reply trackers that were
		 * left in an interrupted syscall state.
		 */
		if (reply->deadline_ns != 0 && !reply->interrupted)
			kdbus_notify_reply_timeout(conn->ep->bus, conn->id,
						   reply->cookie);

		kdbus_reply_unlink(reply);
	}

	/* rearm delayed work with next timeout */
	if (deadline != ~0ULL)
		schedule_delayed_work(&conn->work,
				      nsecs_to_jiffies(deadline - now));

	mutex_unlock(&conn->lock);

	kdbus_notify_flush(conn->ep->bus);
}