Blame view
drivers/greybus/operation.c
35 KB
eb50fd3a2 staging: greybus:... |
1 |
// SPDX-License-Identifier: GPL-2.0 |
e88afa581 greybus: introduc... |
2 3 4 |
/* * Greybus operations * |
d3d2bea16 greybus: clean up... |
5 6 |
* Copyright 2014-2015 Google Inc. * Copyright 2014-2015 Linaro Ltd. |
e88afa581 greybus: introduc... |
7 8 9 10 11 |
*/ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> |
fd7134a3c greybus: operatio... |
12 13 |
#include <linux/sched.h> #include <linux/wait.h> |
e88afa581 greybus: introduc... |
14 |
#include <linux/workqueue.h> |
ec0ad8681 staging: greybus:... |
15 |
#include <linux/greybus.h> |
e88afa581 greybus: introduc... |
16 |
|
5c8ad599b greybus: operatio... |
17 |
#include "greybus_trace.h" |
e88afa581 greybus: introduc... |
18 |
|
5b3db0dda greybus: create a... |
19 |
static struct kmem_cache *gb_operation_cache; |
1e5613b4a greybus: operatio... |
20 |
static struct kmem_cache *gb_message_cache; |
5b3db0dda greybus: create a... |
21 |
|
701615f82 greybus: operatio... |
22 23 |
/* Workqueue to handle Greybus operation completions. */ static struct workqueue_struct *gb_operation_completion_wq; |
fd7134a3c greybus: operatio... |
24 25 |
/* Wait queue for synchronous cancellations. */ static DECLARE_WAIT_QUEUE_HEAD(gb_operation_cancellation_queue); |
d90c25b0a greybus: let oper... |
26 |
/* |
008974cb5 greybus: operatio... |
27 |
* Protects updates to operation->errno. |
82b5e3feb greybus: record t... |
28 |
*/ |
e88afa581 greybus: introduc... |
29 |
static DEFINE_SPINLOCK(gb_operations_lock); |
abb722e79 greybus: operatio... |
30 |
static int gb_operation_response_send(struct gb_operation *operation, |
8478c35a8 staging: greybus:... |
31 |
int errno); |
abb722e79 greybus: operatio... |
32 |
|
008974cb5 greybus: operatio... |
33 34 35 36 37 38 39 |
/* * Increment operation active count and add to connection list unless the * connection is going away. * * Caller holds operation reference. */ static int gb_operation_get_active(struct gb_operation *operation) |
3eeac7e37 greybus: operatio... |
40 |
{ |
008974cb5 greybus: operatio... |
41 42 43 44 |
struct gb_connection *connection = operation->connection; unsigned long flags; spin_lock_irqsave(&connection->lock, flags); |
77bbbcf6d greybus: operatio... |
45 46 47 48 49 50 51 |
switch (connection->state) { case GB_CONNECTION_STATE_ENABLED: break; case GB_CONNECTION_STATE_ENABLED_TX: if (gb_operation_is_incoming(operation)) goto err_unlock; break; |
3de5acfaf greybus: connecti... |
52 53 54 55 |
case GB_CONNECTION_STATE_DISCONNECTING: if (!gb_operation_is_core(operation)) goto err_unlock; break; |
77bbbcf6d greybus: operatio... |
56 57 |
default: goto err_unlock; |
008974cb5 greybus: operatio... |
58 59 60 61 |
} if (operation->active++ == 0) list_add_tail(&operation->links, &connection->operations); |
f866e66f3 greybus: add oper... |
62 |
trace_gb_operation_get_active(operation); |
008974cb5 greybus: operatio... |
63 64 65 |
spin_unlock_irqrestore(&connection->lock, flags); return 0; |
77bbbcf6d greybus: operatio... |
66 67 68 69 70 |
err_unlock: spin_unlock_irqrestore(&connection->lock, flags); return -ENOTCONN; |
3eeac7e37 greybus: operatio... |
71 72 73 |
} /* Caller holds operation reference. */ |
008974cb5 greybus: operatio... |
74 |
static void gb_operation_put_active(struct gb_operation *operation) |
3eeac7e37 greybus: operatio... |
75 |
{ |
008974cb5 greybus: operatio... |
76 77 78 79 |
struct gb_connection *connection = operation->connection; unsigned long flags; spin_lock_irqsave(&connection->lock, flags); |
f866e66f3 greybus: add oper... |
80 |
|
df732546c greybus: tracing:... |
81 |
trace_gb_operation_put_active(operation); |
f866e66f3 greybus: add oper... |
82 |
|
008974cb5 greybus: operatio... |
83 84 |
if (--operation->active == 0) { list_del(&operation->links); |
fd7134a3c greybus: operatio... |
85 86 87 |
if (atomic_read(&operation->waiters)) wake_up(&gb_operation_cancellation_queue); } |
008974cb5 greybus: operatio... |
88 |
spin_unlock_irqrestore(&connection->lock, flags); |
fd7134a3c greybus: operatio... |
89 |
} |
008974cb5 greybus: operatio... |
90 |
static bool gb_operation_is_active(struct gb_operation *operation) |
fd7134a3c greybus: operatio... |
91 |
{ |
008974cb5 greybus: operatio... |
92 93 94 95 96 97 98 99 100 |
struct gb_connection *connection = operation->connection; unsigned long flags; bool ret; spin_lock_irqsave(&connection->lock, flags); ret = operation->active; spin_unlock_irqrestore(&connection->lock, flags); return ret; |
3eeac7e37 greybus: operatio... |
101 |
} |
3deb37d4a greybus: use spec... |
102 |
/* |
2fb2d2a73 greybus: define -... |
103 104 105 106 107 108 109 |
* Set an operation's result. * * Initially an outgoing operation's errno value is -EBADR. * If no error occurs before sending the request message the only * valid value operation->errno can be set to is -EINPROGRESS, * indicating the request has been (or rather is about to be) sent. * At that point nobody should be looking at the result until the |
d50628349 greybus: operatio... |
110 |
* response arrives. |
3deb37d4a greybus: use spec... |
111 112 113 114 115 |
* * The first time the result gets set after the request has been * sent, that result "sticks." That is, if two concurrent threads * race to set the result, the first one wins. The return value * tells the caller whether its result was recorded; if not the |
2fb2d2a73 greybus: define -... |
116 117 118 119 120 121 122 123 124 125 |
* caller has nothing more to do. * * The result value -EILSEQ is reserved to signal an implementation * error; if it's ever observed, the code performing the request has * done something fundamentally wrong. It is an error to try to set * the result to -EBADR, and attempts to do so result in a warning, * and -EILSEQ is used instead. Similarly, the only valid result * value to set for an operation in initial state is -EINPROGRESS. * Attempts to do otherwise will also record a (successful) -EILSEQ * operation result. |
3deb37d4a greybus: use spec... |
126 |
*/ |
abe9a3006 greybus: first op... |
127 |
static bool gb_operation_result_set(struct gb_operation *operation, int result) |
ba986b5ab greybus: encapsul... |
128 |
{ |
184ab534d greybus: operatio... |
129 |
unsigned long flags; |
894cbc313 greybus: update o... |
130 |
int prev; |
3deb37d4a greybus: use spec... |
131 |
if (result == -EINPROGRESS) { |
2fb2d2a73 greybus: define -... |
132 133 134 135 136 137 138 |
/* * -EINPROGRESS is used to indicate the request is * in flight. It should be the first result value * set after the initial -EBADR. Issue a warning * and record an implementation error if it's * set at any other time. */ |
184ab534d greybus: operatio... |
139 |
spin_lock_irqsave(&gb_operations_lock, flags); |
894cbc313 greybus: update o... |
140 141 142 |
prev = operation->errno; if (prev == -EBADR) operation->errno = result; |
2fb2d2a73 greybus: define -... |
143 144 |
else operation->errno = -EILSEQ; |
184ab534d greybus: operatio... |
145 |
spin_unlock_irqrestore(&gb_operations_lock, flags); |
2fb2d2a73 greybus: define -... |
146 |
WARN_ON(prev != -EBADR); |
894cbc313 greybus: update o... |
147 |
|
2fb2d2a73 greybus: define -... |
148 |
return true; |
3deb37d4a greybus: use spec... |
149 |
} |
3deb37d4a greybus: use spec... |
150 |
|
2fb2d2a73 greybus: define -... |
151 152 153 154 155 156 157 158 159 160 161 |
/* * The first result value set after a request has been sent * will be the final result of the operation. Subsequent * attempts to set the result are ignored. * * Note that -EBADR is a reserved "initial state" result * value. Attempts to set this value result in a warning, * and the result code is set to -EILSEQ instead. */ if (WARN_ON(result == -EBADR)) result = -EILSEQ; /* Nobody should be setting -EBADR */ |
184ab534d greybus: operatio... |
162 |
spin_lock_irqsave(&gb_operations_lock, flags); |
894cbc313 greybus: update o... |
163 164 |
prev = operation->errno; if (prev == -EINPROGRESS) |
2fb2d2a73 greybus: define -... |
165 |
operation->errno = result; /* First and final result */ |
184ab534d greybus: operatio... |
166 |
spin_unlock_irqrestore(&gb_operations_lock, flags); |
894cbc313 greybus: update o... |
167 168 |
return prev == -EINPROGRESS; |
ba986b5ab greybus: encapsul... |
169 170 171 172 |
} int gb_operation_result(struct gb_operation *operation) { |
3deb37d4a greybus: use spec... |
173 |
int result = operation->errno; |
2fb2d2a73 greybus: define -... |
174 |
WARN_ON(result == -EBADR); |
3deb37d4a greybus: use spec... |
175 176 177 |
WARN_ON(result == -EINPROGRESS); return result; |
ba986b5ab greybus: encapsul... |
178 |
} |
1dad6b351 greybus: operatio... |
179 |
EXPORT_SYMBOL_GPL(gb_operation_result); |
ba986b5ab greybus: encapsul... |
180 |
|
0581f28ef greybus: operatio... |
181 |
/* |
048a7ffe2 greybus: operatio... |
182 183 |
* Looks up an outgoing operation on a connection and returns a refcounted * pointer if found, or NULL otherwise. |
0581f28ef greybus: operatio... |
184 |
*/ |
84d148b10 greybus: add gb_o... |
185 |
static struct gb_operation * |
048a7ffe2 greybus: operatio... |
186 |
gb_operation_find_outgoing(struct gb_connection *connection, u16 operation_id) |
84d148b10 greybus: add gb_o... |
187 |
{ |
b8616da87 greybus: simplify... |
188 |
struct gb_operation *operation; |
184ab534d greybus: operatio... |
189 |
unsigned long flags; |
84d148b10 greybus: add gb_o... |
190 |
bool found = false; |
008974cb5 greybus: operatio... |
191 |
spin_lock_irqsave(&connection->lock, flags); |
afb2e1342 greybus: get rid ... |
192 |
list_for_each_entry(operation, &connection->operations, links) |
048a7ffe2 greybus: operatio... |
193 |
if (operation->id == operation_id && |
8478c35a8 staging: greybus:... |
194 |
!gb_operation_is_incoming(operation)) { |
0581f28ef greybus: operatio... |
195 |
gb_operation_get(operation); |
84d148b10 greybus: add gb_o... |
196 |
found = true; |
b8616da87 greybus: simplify... |
197 198 |
break; } |
008974cb5 greybus: operatio... |
199 |
spin_unlock_irqrestore(&connection->lock, flags); |
84d148b10 greybus: add gb_o... |
200 201 202 |
return found ? operation : NULL; } |
a52c4352a greybus: operatio... |
203 |
static int gb_message_send(struct gb_message *message, gfp_t gfp) |
374e6a269 greybus: kill off... |
204 |
{ |
3ed67aba9 greybus: stop sto... |
205 |
struct gb_connection *connection = message->operation->connection; |
002fe66a7 greybus: send mes... |
206 |
|
5c8ad599b greybus: operatio... |
207 |
trace_gb_message_send(message); |
3e136cc9e greybus: operatio... |
208 |
return connection->hd->driver->message_send(connection->hd, |
0a9c4d70d greybus: switch c... |
209 |
connection->hd_cport_id, |
7cf7bca9e greybus: pass mes... |
210 |
message, |
b84abdcb4 greybus: operatio... |
211 |
gfp); |
374e6a269 greybus: kill off... |
212 |
} |
6014718d4 greybus: get rid ... |
213 |
/* |
7cf7bca9e greybus: pass mes... |
214 |
* Cancel a message we have passed to the host device layer to be sent. |
6014718d4 greybus: get rid ... |
215 |
*/ |
35b1342bb greybus: cancel m... |
216 |
static void gb_message_cancel(struct gb_message *message) |
374e6a269 greybus: kill off... |
217 |
{ |
2537636ab greybus: hd: rena... |
218 |
struct gb_host_device *hd = message->operation->connection->hd; |
374e6a269 greybus: kill off... |
219 |
|
3e136cc9e greybus: operatio... |
220 |
hd->driver->message_cancel(message); |
374e6a269 greybus: kill off... |
221 |
} |
a9163b2c3 greybus: cancel b... |
222 |
|
2eb585f8d greybus: move rec... |
223 224 |
static void gb_operation_request_handle(struct gb_operation *operation) { |
25cdd7aa5 greybus: operatio... |
225 |
struct gb_connection *connection = operation->connection; |
973ccfd62 greybus: operatio... |
226 |
int status; |
ff65be7a5 greybus: operatio... |
227 |
int ret; |
c3cf27853 greybus: pass ope... |
228 |
|
bfa9a5e2d greybus: connecti... |
229 230 |
if (connection->handler) { status = connection->handler(operation); |
973ccfd62 greybus: operatio... |
231 |
} else { |
25cdd7aa5 greybus: operatio... |
232 |
dev_err(&connection->hd->dev, |
2f3db927c greybus: don't us... |
233 234 |
"%s: unexpected incoming request of type 0x%02x ", |
25cdd7aa5 greybus: operatio... |
235 |
connection->name, operation->type); |
2eb585f8d greybus: move rec... |
236 |
|
973ccfd62 greybus: operatio... |
237 238 |
status = -EPROTONOSUPPORT; } |
ff65be7a5 greybus: operatio... |
239 |
|
973ccfd62 greybus: operatio... |
240 |
ret = gb_operation_response_send(operation, status); |
ff65be7a5 greybus: operatio... |
241 |
if (ret) { |
25cdd7aa5 greybus: operatio... |
242 |
dev_err(&connection->hd->dev, |
2f3db927c greybus: don't us... |
243 244 |
"%s: failed to send response %d for type 0x%02x: %d ", |
25cdd7aa5 greybus: operatio... |
245 |
connection->name, status, operation->type, ret); |
c77bac08e greybus: operatio... |
246 |
return; |
ff65be7a5 greybus: operatio... |
247 |
} |
2eb585f8d greybus: move rec... |
248 |
} |
e88afa581 greybus: introduc... |
249 |
/* |
c600e535a greybus: operatio... |
250 251 252 253 |
* Process operation work. * * For incoming requests, call the protocol request handler. The operation * result should be -EINPROGRESS at this point. |
d4a1ff674 greybus: activate... |
254 255 256 257 258 |
* * For outgoing requests, the operation result value should have * been set before queueing this. The operation callback function * allows the original requester to know the request has completed * and its result is available. |
e88afa581 greybus: introduc... |
259 |
*/ |
ee637a9b0 greybus: abandon ... |
260 |
static void gb_operation_work(struct work_struct *work) |
e88afa581 greybus: introduc... |
261 |
{ |
84d148b10 greybus: add gb_o... |
262 |
struct gb_operation *operation; |
dbec27298 staging: greybus:... |
263 |
int ret; |
84d148b10 greybus: add gb_o... |
264 |
|
ee637a9b0 greybus: abandon ... |
265 |
operation = container_of(work, struct gb_operation, work); |
37754030b greybus: operatio... |
266 |
|
dbec27298 staging: greybus:... |
267 |
if (gb_operation_is_incoming(operation)) { |
c600e535a greybus: operatio... |
268 |
gb_operation_request_handle(operation); |
dbec27298 staging: greybus:... |
269 270 271 272 273 274 275 |
} else { ret = del_timer_sync(&operation->timer); if (!ret) { /* Cancel request message if scheduled by timeout. */ if (gb_operation_result(operation) == -ETIMEDOUT) gb_message_cancel(operation->request); } |
c600e535a greybus: operatio... |
276 |
operation->callback(operation); |
dbec27298 staging: greybus:... |
277 |
} |
37754030b greybus: operatio... |
278 |
|
3eeac7e37 greybus: operatio... |
279 |
gb_operation_put_active(operation); |
10c693990 greybus: rework s... |
280 |
gb_operation_put(operation); |
2eb585f8d greybus: move rec... |
281 |
} |
e99e88a9d treewide: setup_t... |
282 |
static void gb_operation_timeout(struct timer_list *t) |
dbec27298 staging: greybus:... |
283 |
{ |
e99e88a9d treewide: setup_t... |
284 |
struct gb_operation *operation = from_timer(operation, t, timer); |
dbec27298 staging: greybus:... |
285 286 287 288 289 290 291 292 293 |
if (gb_operation_result_set(operation, -ETIMEDOUT)) { /* * A stuck request message will be cancelled from the * workqueue. */ queue_work(gb_operation_completion_wq, &operation->work); } } |
2537636ab greybus: hd: rena... |
294 |
static void gb_operation_message_init(struct gb_host_device *hd, |
8478c35a8 staging: greybus:... |
295 296 297 |
struct gb_message *message, u16 operation_id, size_t payload_size, u8 type) |
dc779229b greybus: introduc... |
298 299 |
{ struct gb_operation_msg_hdr *header; |
dc779229b greybus: introduc... |
300 |
|
24ef48539 greybus: drop hos... |
301 |
header = message->buffer; |
dc779229b greybus: introduc... |
302 303 |
message->header = header; |
746e0ef95 greybus: use null... |
304 |
message->payload = payload_size ? header + 1 : NULL; |
7cfa69955 greybus: only rec... |
305 |
message->payload_size = payload_size; |
dc779229b greybus: introduc... |
306 307 308 |
/* * The type supplied for incoming message buffers will be |
7adb32b42 greybus: operatio... |
309 310 |
* GB_REQUEST_TYPE_INVALID. Such buffers will be overwritten by * arriving data so there's no need to initialize the message header. |
dc779229b greybus: introduc... |
311 |
*/ |
7adb32b42 greybus: operatio... |
312 |
if (type != GB_REQUEST_TYPE_INVALID) { |
7cfa69955 greybus: only rec... |
313 |
u16 message_size = (u16)(sizeof(*header) + payload_size); |
dc779229b greybus: introduc... |
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
/* * For a request, the operation id gets filled in * when the message is sent. For a response, it * will be copied from the request by the caller. * * The result field in a request message must be * zero. It will be set just prior to sending for * a response. */ header->size = cpu_to_le16(message_size); header->operation_id = 0; header->type = type; header->result = 0; } } |
d98b52b04 greybus: define g... |
329 |
/* |
ea64cd9a5 greybus: use oper... |
330 331 332 333 334 335 336 337 |
* Allocate a message to be used for an operation request or response. * Both types of message contain a common header. The request message * for an outgoing operation is outbound, as is the response message * for an incoming operation. The message header for an outbound * message is partially initialized here. * * The headers for inbound messages don't need to be initialized; * they'll be filled in by arriving data. |
87d208feb greybus: embed me... |
338 |
* |
1e5613b4a greybus: operatio... |
339 |
* Our message buffers have the following layout: |
87d208feb greybus: embed me... |
340 341 |
* message header \_ these combined are * message payload / the message size |
22b320f40 greybus: add resp... |
342 |
*/ |
c08b1ddae greybus: dynamica... |
343 |
static struct gb_message * |
2537636ab greybus: hd: rena... |
344 |
gb_operation_message_alloc(struct gb_host_device *hd, u8 type, |
8478c35a8 staging: greybus:... |
345 |
size_t payload_size, gfp_t gfp_flags) |
22b320f40 greybus: add resp... |
346 |
{ |
c7f82d5dc greybus: start us... |
347 |
struct gb_message *message; |
22b320f40 greybus: add resp... |
348 |
struct gb_operation_msg_hdr *header; |
87d208feb greybus: embed me... |
349 |
size_t message_size = payload_size + sizeof(*header); |
22b320f40 greybus: add resp... |
350 |
|
1e5613b4a greybus: operatio... |
351 |
if (message_size > hd->buffer_size_max) { |
b427572eb greybus: core: ad... |
352 353 |
dev_warn(&hd->dev, "requested message size too big (%zu > %zu) ", |
8478c35a8 staging: greybus:... |
354 |
message_size, hd->buffer_size_max); |
1e5613b4a greybus: operatio... |
355 |
return NULL; |
0cffcac30 greybus: create a... |
356 |
} |
1e5613b4a greybus: operatio... |
357 358 359 |
/* Allocate the message structure and buffer. */ message = kmem_cache_zalloc(gb_message_cache, gfp_flags); |
c08b1ddae greybus: dynamica... |
360 361 |
if (!message) return NULL; |
22b320f40 greybus: add resp... |
362 |
|
24ef48539 greybus: drop hos... |
363 |
message->buffer = kzalloc(message_size, gfp_flags); |
1e5613b4a greybus: operatio... |
364 365 |
if (!message->buffer) goto err_free_message; |
dc779229b greybus: introduc... |
366 |
/* Initialize the message. Operation id is filled in later. */ |
7cfa69955 greybus: only rec... |
367 |
gb_operation_message_init(hd, message, 0, payload_size, type); |
ea64cd9a5 greybus: use oper... |
368 |
|
c08b1ddae greybus: dynamica... |
369 |
return message; |
1e5613b4a greybus: operatio... |
370 371 372 373 374 |
err_free_message: kmem_cache_free(gb_message_cache, message); return NULL; |
c7f82d5dc greybus: start us... |
375 |
} |
c08b1ddae greybus: dynamica... |
376 |
static void gb_operation_message_free(struct gb_message *message) |
c7f82d5dc greybus: start us... |
377 |
{ |
1e5613b4a greybus: operatio... |
378 379 |
kfree(message->buffer); kmem_cache_free(gb_message_cache, message); |
22b320f40 greybus: add resp... |
380 381 382 |
} /* |
696e0ccab greybus: Random s... |
383 384 |
* Map an enum gb_operation_status value (which is represented in a * message as a single byte) to an appropriate Linux negative errno. |
bc717fcbf greybus: define g... |
385 |
*/ |
0c90fff4e greybus: introduc... |
386 |
static int gb_operation_status_map(u8 status) |
bc717fcbf greybus: define g... |
387 388 389 390 |
{ switch (status) { case GB_OP_SUCCESS: return 0; |
bc717fcbf greybus: define g... |
391 392 |
case GB_OP_INTERRUPTED: return -EINTR; |
57248face greybus: renumber... |
393 394 395 396 |
case GB_OP_TIMEOUT: return -ETIMEDOUT; case GB_OP_NO_MEMORY: return -ENOMEM; |
bc717fcbf greybus: define g... |
397 398 399 |
case GB_OP_PROTOCOL_BAD: return -EPROTONOSUPPORT; case GB_OP_OVERFLOW: |
1a365154c greybus: fix some... |
400 |
return -EMSGSIZE; |
57248face greybus: renumber... |
401 402 403 404 |
case GB_OP_INVALID: return -EINVAL; case GB_OP_RETRY: return -EAGAIN; |
aa26351d0 greybus: define G... |
405 406 |
case GB_OP_NONEXISTENT: return -ENODEV; |
57248face greybus: renumber... |
407 408 409 |
case GB_OP_MALFUNCTION: return -EILSEQ; case GB_OP_UNKNOWN_ERROR: |
bc717fcbf greybus: define g... |
410 411 412 413 414 415 |
default: return -EIO; } } /* |
0c90fff4e greybus: introduc... |
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
* Map a Linux errno value (from operation->errno) into the value * that should represent it in a response message status sent * over the wire. Returns an enum gb_operation_status value (which * is represented in a message as a single byte). */ static u8 gb_operation_errno_map(int errno) { switch (errno) { case 0: return GB_OP_SUCCESS; case -EINTR: return GB_OP_INTERRUPTED; case -ETIMEDOUT: return GB_OP_TIMEOUT; case -ENOMEM: return GB_OP_NO_MEMORY; case -EPROTONOSUPPORT: return GB_OP_PROTOCOL_BAD; case -EMSGSIZE: return GB_OP_OVERFLOW; /* Could be underflow too */ case -EINVAL: return GB_OP_INVALID; case -EAGAIN: return GB_OP_RETRY; case -EILSEQ: return GB_OP_MALFUNCTION; |
aa26351d0 greybus: define G... |
442 443 |
case -ENODEV: return GB_OP_NONEXISTENT; |
0c90fff4e greybus: introduc... |
444 445 446 447 448 |
case -EIO: default: return GB_OP_UNKNOWN_ERROR; } } |
82e26f73b greybus: send ope... |
449 |
bool gb_operation_response_alloc(struct gb_operation *operation, |
8478c35a8 staging: greybus:... |
450 |
size_t response_size, gfp_t gfp) |
82e26f73b greybus: send ope... |
451 |
{ |
2537636ab greybus: hd: rena... |
452 |
struct gb_host_device *hd = operation->connection->hd; |
82e26f73b greybus: send ope... |
453 454 455 |
struct gb_operation_msg_hdr *request_header; struct gb_message *response; u8 type; |
6d653370c greybus: eliminat... |
456 |
type = operation->type | GB_MESSAGE_TYPE_RESPONSE; |
1c7658cf5 greybus: operatio... |
457 |
response = gb_operation_message_alloc(hd, type, response_size, gfp); |
82e26f73b greybus: send ope... |
458 459 460 461 462 463 464 465 466 467 |
if (!response) return false; response->operation = operation; /* * Size and type get initialized when the message is * allocated. The errno will be set before sending. All * that's left is the operation id, which we copy from the * request message header (as-is, in little-endian order). */ |
82b5e3feb greybus: record t... |
468 |
request_header = operation->request->header; |
82e26f73b greybus: send ope... |
469 470 471 472 473 |
response->header->operation_id = request_header->operation_id; operation->response = response; return true; } |
1dad6b351 greybus: operatio... |
474 |
EXPORT_SYMBOL_GPL(gb_operation_response_alloc); |
82e26f73b greybus: send ope... |
475 |
|
0c90fff4e greybus: introduc... |
476 |
/* |
22b320f40 greybus: add resp... |
477 |
* Create a Greybus operation to be sent over the given connection. |
696e0ccab greybus: Random s... |
478 |
* The request buffer will be big enough for a payload of the given |
ea64cd9a5 greybus: use oper... |
479 480 481 482 483 484 485 486 |
* size. * * For outgoing requests, the request message's header will be * initialized with the type of the request and the message size. * Outgoing operations must also specify the response buffer size, * which must be sufficient to hold all expected response data. The * response message header will eventually be overwritten, so there's * no need to initialize it here. |
22b320f40 greybus: add resp... |
487 |
* |
ea64cd9a5 greybus: use oper... |
488 489 490 491 492 493 |
* Request messages for incoming operations can arrive in interrupt * context, so they must be allocated with GFP_ATOMIC. In this case * the request buffer will be immediately overwritten, so there is * no need to initialize the message header. Responsibility for * allocating a response buffer lies with the incoming request * handler for a protocol. So we don't allocate that here. |
e88afa581 greybus: introduc... |
494 |
* |
22b320f40 greybus: add resp... |
495 496 |
* Returns a pointer to the new operation or a null pointer if an * error occurs. |
e88afa581 greybus: introduc... |
497 |
*/ |
30a2964f8 greybus: distingu... |
498 |
static struct gb_operation * |
ea64cd9a5 greybus: use oper... |
499 |
gb_operation_create_common(struct gb_connection *connection, u8 type, |
8478c35a8 staging: greybus:... |
500 501 |
size_t request_size, size_t response_size, unsigned long op_flags, gfp_t gfp_flags) |
e88afa581 greybus: introduc... |
502 |
{ |
2537636ab greybus: hd: rena... |
503 |
struct gb_host_device *hd = connection->hd; |
e88afa581 greybus: introduc... |
504 |
struct gb_operation *operation; |
e88afa581 greybus: introduc... |
505 |
|
5b3db0dda greybus: create a... |
506 |
operation = kmem_cache_zalloc(gb_operation_cache, gfp_flags); |
e88afa581 greybus: introduc... |
507 508 |
if (!operation) return NULL; |
6507cced6 greybus: FIXME/XX... |
509 |
operation->connection = connection; |
e88afa581 greybus: introduc... |
510 |
|
c08b1ddae greybus: dynamica... |
511 512 513 |
operation->request = gb_operation_message_alloc(hd, type, request_size, gfp_flags); if (!operation->request) |
5b3db0dda greybus: create a... |
514 |
goto err_cache; |
c08b1ddae greybus: dynamica... |
515 |
operation->request->operation = operation; |
22b320f40 greybus: add resp... |
516 |
|
ea64cd9a5 greybus: use oper... |
517 |
/* Allocate the response buffer for outgoing operations */ |
710067e2e greybus: operatio... |
518 |
if (!(op_flags & GB_OPERATION_FLAG_INCOMING)) { |
1c7658cf5 greybus: operatio... |
519 520 |
if (!gb_operation_response_alloc(operation, response_size, gfp_flags)) { |
5b3db0dda greybus: create a... |
521 |
goto err_request; |
1c7658cf5 greybus: operatio... |
522 |
} |
dbec27298 staging: greybus:... |
523 |
|
e99e88a9d treewide: setup_t... |
524 |
timer_setup(&operation->timer, gb_operation_timeout, 0); |
82b5e3feb greybus: record t... |
525 |
} |
710067e2e greybus: operatio... |
526 527 528 |
operation->flags = op_flags; operation->type = type; |
3deb37d4a greybus: use spec... |
529 |
operation->errno = -EBADR; /* Initial value--means "never set" */ |
e88afa581 greybus: introduc... |
530 |
|
ee637a9b0 greybus: abandon ... |
531 |
INIT_WORK(&operation->work, gb_operation_work); |
e88afa581 greybus: introduc... |
532 |
init_completion(&operation->completion); |
c7d0f258f greybus: referenc... |
533 |
kref_init(&operation->kref); |
fd7134a3c greybus: operatio... |
534 |
atomic_set(&operation->waiters, 0); |
e88afa581 greybus: introduc... |
535 |
|
e88afa581 greybus: introduc... |
536 |
return operation; |
5b3db0dda greybus: create a... |
537 538 |
err_request: |
c08b1ddae greybus: dynamica... |
539 |
gb_operation_message_free(operation->request); |
5b3db0dda greybus: create a... |
540 541 542 543 |
err_cache: kmem_cache_free(gb_operation_cache, operation); return NULL; |
e88afa581 greybus: introduc... |
544 |
} |
55f66a88d greybus: enforce ... |
545 546 547 548 549 550 551 552 |
/* * Create a new operation associated with the given connection. The * request and response sizes provided are the number of bytes * required to hold the request/response payload only. Both of * these are allowed to be 0. Note that 0x00 is reserved as an * invalid operation type for all protocols, and this is enforced * here. */ |
7e43e337a greybus: operatio... |
553 554 |
struct gb_operation * gb_operation_create_flags(struct gb_connection *connection, |
8478c35a8 staging: greybus:... |
555 556 557 |
u8 type, size_t request_size, size_t response_size, unsigned long flags, gfp_t gfp) |
30a2964f8 greybus: distingu... |
558 |
{ |
f866e66f3 greybus: add oper... |
559 |
struct gb_operation *operation; |
7adb32b42 greybus: operatio... |
560 |
if (WARN_ON_ONCE(type == GB_REQUEST_TYPE_INVALID)) |
55f66a88d greybus: enforce ... |
561 |
return NULL; |
6d653370c greybus: eliminat... |
562 563 |
if (WARN_ON_ONCE(type & GB_MESSAGE_TYPE_RESPONSE)) type &= ~GB_MESSAGE_TYPE_RESPONSE; |
55f66a88d greybus: enforce ... |
564 |
|
7e43e337a greybus: operatio... |
565 566 |
if (WARN_ON_ONCE(flags & ~GB_OPERATION_FLAG_USER_MASK)) flags &= GB_OPERATION_FLAG_USER_MASK; |
f866e66f3 greybus: add oper... |
567 |
operation = gb_operation_create_common(connection, type, |
8478c35a8 staging: greybus:... |
568 569 |
request_size, response_size, flags, gfp); |
f866e66f3 greybus: add oper... |
570 571 572 573 |
if (operation) trace_gb_operation_create(operation); return operation; |
30a2964f8 greybus: distingu... |
574 |
} |
7e43e337a greybus: operatio... |
575 |
EXPORT_SYMBOL_GPL(gb_operation_create_flags); |
30a2964f8 greybus: distingu... |
576 |
|
18079ece8 greybus: operatio... |
577 578 |
struct gb_operation * gb_operation_create_core(struct gb_connection *connection, |
8478c35a8 staging: greybus:... |
579 580 581 |
u8 type, size_t request_size, size_t response_size, unsigned long flags, gfp_t gfp) |
18079ece8 greybus: operatio... |
582 583 584 585 586 587 |
{ struct gb_operation *operation; flags |= GB_OPERATION_FLAG_CORE; operation = gb_operation_create_common(connection, type, |
8478c35a8 staging: greybus:... |
588 589 |
request_size, response_size, flags, gfp); |
18079ece8 greybus: operatio... |
590 591 592 593 594 |
if (operation) trace_gb_operation_create_core(operation); return operation; } |
8478c35a8 staging: greybus:... |
595 |
|
18079ece8 greybus: operatio... |
596 |
/* Do not export this function. */ |
d52b35f6b greybus: operatio... |
597 598 |
size_t gb_operation_get_payload_size_max(struct gb_connection *connection) { |
2537636ab greybus: hd: rena... |
599 |
struct gb_host_device *hd = connection->hd; |
d52b35f6b greybus: operatio... |
600 601 602 603 |
return hd->buffer_size_max - sizeof(struct gb_operation_msg_hdr); } EXPORT_SYMBOL_GPL(gb_operation_get_payload_size_max); |
30a2964f8 greybus: distingu... |
604 |
static struct gb_operation * |
ea64cd9a5 greybus: use oper... |
605 |
gb_operation_create_incoming(struct gb_connection *connection, u16 id, |
8478c35a8 staging: greybus:... |
606 |
u8 type, void *data, size_t size) |
30a2964f8 greybus: distingu... |
607 |
{ |
34db1f91e greybus: move cop... |
608 |
struct gb_operation *operation; |
cfa79699c greybus: operatio... |
609 |
size_t request_size; |
710067e2e greybus: operatio... |
610 |
unsigned long flags = GB_OPERATION_FLAG_INCOMING; |
cfa79699c greybus: operatio... |
611 612 613 |
/* Caller has made sure we at least have a message header. */ request_size = size - sizeof(struct gb_operation_msg_hdr); |
34db1f91e greybus: move cop... |
614 |
|
e3398811c greybus: operatio... |
615 616 |
if (!id) flags |= GB_OPERATION_FLAG_UNIDIRECTIONAL; |
710067e2e greybus: operatio... |
617 |
operation = gb_operation_create_common(connection, type, |
8478c35a8 staging: greybus:... |
618 619 620 |
request_size, GB_REQUEST_TYPE_INVALID, flags, GFP_ATOMIC); |
9a586bd2b greybus: operatio... |
621 622 623 624 625 |
if (!operation) return NULL; operation->id = id; memcpy(operation->request->header, data, size); |
f866e66f3 greybus: add oper... |
626 |
trace_gb_operation_create_incoming(operation); |
34db1f91e greybus: move cop... |
627 628 |
return operation; |
30a2964f8 greybus: distingu... |
629 |
} |
e88afa581 greybus: introduc... |
630 |
/* |
deb4b9efb greybus: add a re... |
631 632 633 634 635 636 |
* Get an additional reference on an operation. */ void gb_operation_get(struct gb_operation *operation) { kref_get(&operation->kref); } |
1dad6b351 greybus: operatio... |
637 |
EXPORT_SYMBOL_GPL(gb_operation_get); |
deb4b9efb greybus: add a re... |
638 639 |
/* |
e88afa581 greybus: introduc... |
640 641 |
* Destroy a previously created operation. */ |
c7d0f258f greybus: referenc... |
642 |
static void _gb_operation_destroy(struct kref *kref) |
e88afa581 greybus: introduc... |
643 |
{ |
c7d0f258f greybus: referenc... |
644 645 646 |
struct gb_operation *operation; operation = container_of(kref, struct gb_operation, kref); |
e88afa581 greybus: introduc... |
647 |
|
f866e66f3 greybus: add oper... |
648 |
trace_gb_operation_destroy(operation); |
948966768 greybus: operatio... |
649 650 |
if (operation->response) gb_operation_message_free(operation->response); |
c08b1ddae greybus: dynamica... |
651 |
gb_operation_message_free(operation->request); |
e88afa581 greybus: introduc... |
652 |
|
5b3db0dda greybus: create a... |
653 |
kmem_cache_free(gb_operation_cache, operation); |
e88afa581 greybus: introduc... |
654 |
} |
d90c25b0a greybus: let oper... |
655 |
|
deb4b9efb greybus: add a re... |
656 657 658 659 |
/* * Drop a reference on an operation, and destroy it when the last * one is gone. */ |
c7d0f258f greybus: referenc... |
660 661 |
void gb_operation_put(struct gb_operation *operation) { |
85109f7dd greybus: operatio... |
662 663 |
if (WARN_ON(!operation)) return; |
008974cb5 greybus: operatio... |
664 |
kref_put(&operation->kref, _gb_operation_destroy); |
c7d0f258f greybus: referenc... |
665 |
} |
df469a942 greybus: export n... |
666 |
EXPORT_SYMBOL_GPL(gb_operation_put); |
c7d0f258f greybus: referenc... |
667 |
|
10c693990 greybus: rework s... |
668 669 670 671 672 |
/* Tell the requester we're done */ static void gb_operation_sync_callback(struct gb_operation *operation) { complete(&operation->completion); } |
613c15e86 greybus: operatio... |
673 674 675 676 |
/** * gb_operation_request_send() - send an operation request message * @operation: the operation to initiate * @callback: the operation completion callback |
dbec27298 staging: greybus:... |
677 |
* @timeout: operation timeout in milliseconds, or zero for no timeout |
613c15e86 greybus: operatio... |
678 679 680 681 |
* @gfp: the memory flags to use for any allocations * * The caller has filled in any payload so the request message is ready to go. * The callback function supplied will be called when the response message has |
3e2ee2c1e greybus: operatio... |
682 683 684 685 |
* arrived, a unidirectional request has been sent, or the operation is * cancelled, indicating that the operation is complete. The callback function * can fetch the result of the operation using gb_operation_result() if * desired. |
613c15e86 greybus: operatio... |
686 687 688 |
* * Return: 0 if the request was successfully queued in the host-driver queues, * or a negative errno. |
d90c25b0a greybus: let oper... |
689 690 |
*/ int gb_operation_request_send(struct gb_operation *operation, |
8478c35a8 staging: greybus:... |
691 692 693 |
gb_operation_callback callback, unsigned int timeout, gfp_t gfp) |
d90c25b0a greybus: let oper... |
694 |
{ |
afb2e1342 greybus: get rid ... |
695 696 |
struct gb_connection *connection = operation->connection; struct gb_operation_msg_hdr *header; |
4afb7fd01 greybus: make op_... |
697 |
unsigned int cycle; |
ea2c2ee80 greybus: operatio... |
698 |
int ret; |
d90c25b0a greybus: let oper... |
699 |
|
ca1f8f809 greybus: connecti... |
700 701 |
if (gb_connection_is_offloaded(connection)) return -EBUSY; |
37754030b greybus: operatio... |
702 703 |
if (!callback) return -EINVAL; |
3e2ee2c1e greybus: operatio... |
704 |
|
c25572ca9 greybus: introduc... |
705 706 707 708 709 710 |
/* * Record the callback function, which is executed in * non-atomic (workqueue) context when the final result * of an operation has been set. */ operation->callback = callback; |
afb2e1342 greybus: get rid ... |
711 712 713 |
/* * Assign the operation's id, and store it in the request header. |
3e2ee2c1e greybus: operatio... |
714 |
* Zero is a reserved operation id for unidirectional operations. |
afb2e1342 greybus: get rid ... |
715 |
*/ |
3e2ee2c1e greybus: operatio... |
716 717 718 719 720 721 |
if (gb_operation_is_unidirectional(operation)) { operation->id = 0; } else { cycle = (unsigned int)atomic_inc_return(&connection->op_cycle); operation->id = (u16)(cycle % U16_MAX + 1); } |
afb2e1342 greybus: get rid ... |
722 723 |
header = operation->request->header; header->operation_id = cpu_to_le16(operation->id); |
e8b48d158 greybus: fix a ti... |
724 |
|
3deb37d4a greybus: use spec... |
725 |
gb_operation_result_set(operation, -EINPROGRESS); |
c25572ca9 greybus: introduc... |
726 |
|
3325a4ad7 greybus: operatio... |
727 728 729 730 731 732 733 734 |
/* * Get an extra reference on the operation. It'll be dropped when the * operation completes. */ gb_operation_get(operation); ret = gb_operation_get_active(operation); if (ret) goto err_put; |
a52c4352a greybus: operatio... |
735 |
ret = gb_message_send(operation->request, gfp); |
008974cb5 greybus: operatio... |
736 737 |
if (ret) goto err_put_active; |
dbec27298 staging: greybus:... |
738 739 740 741 |
if (timeout) { operation->timer.expires = jiffies + msecs_to_jiffies(timeout); add_timer(&operation->timer); } |
008974cb5 greybus: operatio... |
742 743 744 745 746 747 |
return 0; err_put_active: gb_operation_put_active(operation); err_put: gb_operation_put(operation); |
ea2c2ee80 greybus: operatio... |
748 749 |
return ret; |
c25572ca9 greybus: introduc... |
750 |
} |
1dad6b351 greybus: operatio... |
751 |
EXPORT_SYMBOL_GPL(gb_operation_request_send); |
c25572ca9 greybus: introduc... |
752 753 754 755 756 757 758 |
/* * Send a synchronous operation. This function is expected to * block, returning only when the response has arrived, (or when an * error is detected. The return value is the result of the * operation. */ |
4f2c08aba greybus: operatio... |
759 |
int gb_operation_request_send_sync_timeout(struct gb_operation *operation, |
8478c35a8 staging: greybus:... |
760 |
unsigned int timeout) |
c25572ca9 greybus: introduc... |
761 762 |
{ int ret; |
a52c4352a greybus: operatio... |
763 |
ret = gb_operation_request_send(operation, gb_operation_sync_callback, |
dbec27298 staging: greybus:... |
764 |
timeout, GFP_KERNEL); |
c25572ca9 greybus: introduc... |
765 |
if (ret) |
d90c25b0a greybus: let oper... |
766 |
return ret; |
8350e7a01 greybus: move tim... |
767 |
|
dbec27298 staging: greybus:... |
768 |
ret = wait_for_completion_interruptible(&operation->completion); |
7bad4e85b greybus: gb_opera... |
769 770 |
if (ret < 0) { /* Cancel the operation if interrupted */ |
1a365154c greybus: fix some... |
771 |
gb_operation_cancel(operation, -ECANCELED); |
7bad4e85b greybus: gb_opera... |
772 |
} |
2cf72a233 greybus: kill gb_... |
773 |
|
ba986b5ab greybus: encapsul... |
774 |
return gb_operation_result(operation); |
d90c25b0a greybus: let oper... |
775 |
} |
4f2c08aba greybus: operatio... |
776 |
EXPORT_SYMBOL_GPL(gb_operation_request_send_sync_timeout); |
d90c25b0a greybus: let oper... |
777 778 |
/* |
82e26f73b greybus: send ope... |
779 780 781 782 783 784 785 |
* Send a response for an incoming operation request. A non-zero * errno indicates a failed operation. * * If there is any response payload, the incoming request handler is * responsible for allocating the response message. Otherwise the * it can simply supply the result errno; this function will * allocate the response message if necessary. |
d90c25b0a greybus: let oper... |
786 |
*/ |
abb722e79 greybus: operatio... |
787 |
static int gb_operation_response_send(struct gb_operation *operation, |
8478c35a8 staging: greybus:... |
788 |
int errno) |
d90c25b0a greybus: let oper... |
789 |
{ |
e1baa3f0a greybus: operatio... |
790 |
struct gb_connection *connection = operation->connection; |
0fb5acc40 greybus: operatio... |
791 |
int ret; |
fde7382b4 greybus: operatio... |
792 |
if (!operation->response && |
8478c35a8 staging: greybus:... |
793 |
!gb_operation_is_unidirectional(operation)) { |
1c7658cf5 greybus: operatio... |
794 |
if (!gb_operation_response_alloc(operation, 0, GFP_KERNEL)) |
fde7382b4 greybus: operatio... |
795 796 |
return -ENOMEM; } |
d2d2c0fe7 greybus: set resu... |
797 798 |
/* Record the result */ if (!gb_operation_result_set(operation, errno)) { |
25cdd7aa5 greybus: operatio... |
799 800 |
dev_err(&connection->hd->dev, "request result already set "); |
d2d2c0fe7 greybus: set resu... |
801 802 |
return -EIO; /* Shouldn't happen */ } |
d90c25b0a greybus: let oper... |
803 |
|
1d771fe41 greybus: operatio... |
804 |
/* Sender of request does not care about response. */ |
e3398811c greybus: operatio... |
805 |
if (gb_operation_is_unidirectional(operation)) |
1d771fe41 greybus: operatio... |
806 |
return 0; |
0fb5acc40 greybus: operatio... |
807 808 |
/* Reference will be dropped when message has been sent. */ gb_operation_get(operation); |
008974cb5 greybus: operatio... |
809 810 811 |
ret = gb_operation_get_active(operation); if (ret) goto err_put; |
0fb5acc40 greybus: operatio... |
812 |
|
82e26f73b greybus: send ope... |
813 814 |
/* Fill in the response header and send it */ operation->response->header->result = gb_operation_errno_map(errno); |
a52c4352a greybus: operatio... |
815 |
ret = gb_message_send(operation->response, GFP_KERNEL); |
008974cb5 greybus: operatio... |
816 817 818 819 820 821 822 823 824 |
if (ret) goto err_put_active; return 0; err_put_active: gb_operation_put_active(operation); err_put: gb_operation_put(operation); |
0fb5acc40 greybus: operatio... |
825 826 |
return ret; |
d90c25b0a greybus: let oper... |
827 |
} |
2eb585f8d greybus: move rec... |
828 |
/* |
7cf7bca9e greybus: pass mes... |
829 |
* This function is called when a message send request has completed. |
d98b52b04 greybus: define g... |
830 |
*/ |
2537636ab greybus: hd: rena... |
831 |
void greybus_message_sent(struct gb_host_device *hd, |
8478c35a8 staging: greybus:... |
832 |
struct gb_message *message, int status) |
d98b52b04 greybus: define g... |
833 |
{ |
a4e08469e greybus: operatio... |
834 835 |
struct gb_operation *operation = message->operation; struct gb_connection *connection = operation->connection; |
d98b52b04 greybus: define g... |
836 |
|
d4a1ff674 greybus: activate... |
837 838 839 840 841 |
/* * If the message was a response, we just need to drop our * reference to the operation. If an error occurred, report * it. * |
3e2ee2c1e greybus: operatio... |
842 843 844 845 846 |
* For requests, if there's no error and the operation in not * unidirectional, there's nothing more to do until the response * arrives. If an error occurred attempting to send it, or if the * operation is unidrectional, record the result of the operation and * schedule its completion. |
d4a1ff674 greybus: activate... |
847 |
*/ |
d4a1ff674 greybus: activate... |
848 |
if (message == operation->response) { |
e1baa3f0a greybus: operatio... |
849 |
if (status) { |
25cdd7aa5 greybus: operatio... |
850 |
dev_err(&connection->hd->dev, |
2f3db927c greybus: don't us... |
851 852 |
"%s: error sending response 0x%02x: %d ", |
25cdd7aa5 greybus: operatio... |
853 |
connection->name, operation->type, status); |
e1baa3f0a greybus: operatio... |
854 |
} |
3e2ee2c1e greybus: operatio... |
855 |
|
3eeac7e37 greybus: operatio... |
856 |
gb_operation_put_active(operation); |
d4a1ff674 greybus: activate... |
857 |
gb_operation_put(operation); |
3e2ee2c1e greybus: operatio... |
858 |
} else if (status || gb_operation_is_unidirectional(operation)) { |
701615f82 greybus: operatio... |
859 860 |
if (gb_operation_result_set(operation, status)) { queue_work(gb_operation_completion_wq, |
8478c35a8 staging: greybus:... |
861 |
&operation->work); |
701615f82 greybus: operatio... |
862 |
} |
d4a1ff674 greybus: activate... |
863 |
} |
d98b52b04 greybus: define g... |
864 |
} |
7cf7bca9e greybus: pass mes... |
865 |
EXPORT_SYMBOL_GPL(greybus_message_sent); |
d98b52b04 greybus: define g... |
866 867 |
/* |
d37b1db13 greybus: refactor... |
868 869 |
* We've received data on a connection, and it doesn't look like a * response, so we assume it's a request. |
78496db01 greybus: clean up... |
870 871 |
* * This is called in interrupt context, so just copy the incoming |
d37b1db13 greybus: refactor... |
872 873 |
* data into the request buffer and handle the rest via workqueue. */ |
85a044289 greybus: operatio... |
874 |
static void gb_connection_recv_request(struct gb_connection *connection, |
2321f049a greybus: operatio... |
875 876 |
const struct gb_operation_msg_hdr *header, void *data, size_t size) |
d37b1db13 greybus: refactor... |
877 878 |
{ struct gb_operation *operation; |
2321f049a greybus: operatio... |
879 880 |
u16 operation_id; u8 type; |
008974cb5 greybus: operatio... |
881 |
int ret; |
d37b1db13 greybus: refactor... |
882 |
|
2321f049a greybus: operatio... |
883 884 |
operation_id = le16_to_cpu(header->operation_id); type = header->type; |
34db1f91e greybus: move cop... |
885 |
operation = gb_operation_create_incoming(connection, operation_id, |
8478c35a8 staging: greybus:... |
886 |
type, data, size); |
d37b1db13 greybus: refactor... |
887 |
if (!operation) { |
25cdd7aa5 greybus: operatio... |
888 889 890 891 |
dev_err(&connection->hd->dev, "%s: can't create incoming operation ", connection->name); |
ff65e20ee greybus: operatio... |
892 |
return; |
d37b1db13 greybus: refactor... |
893 |
} |
d37b1db13 greybus: refactor... |
894 |
|
008974cb5 greybus: operatio... |
895 896 897 898 899 |
ret = gb_operation_get_active(operation); if (ret) { gb_operation_put(operation); return; } |
5c8ad599b greybus: operatio... |
900 |
trace_gb_message_recv_request(operation->request); |
3eeac7e37 greybus: operatio... |
901 |
|
d4a1ff674 greybus: activate... |
902 |
/* |
c600e535a greybus: operatio... |
903 904 |
* The initial reference to the operation will be dropped when the * request handler returns. |
d4a1ff674 greybus: activate... |
905 |
*/ |
d4a1ff674 greybus: activate... |
906 |
if (gb_operation_result_set(operation, -EINPROGRESS)) |
5a5bc354c greybus: operatio... |
907 |
queue_work(connection->wq, &operation->work); |
d37b1db13 greybus: refactor... |
908 909 910 911 912 |
} /* * We've received data that appears to be an operation response * message. Look up the operation, and record that we've received |
696e0ccab greybus: Random s... |
913 |
* its response. |
78496db01 greybus: clean up... |
914 |
* |
d37b1db13 greybus: refactor... |
915 916 917 918 |
* This is called in interrupt context, so just copy the incoming * data into the response buffer and handle the rest via workqueue. */ static void gb_connection_recv_response(struct gb_connection *connection, |
dfcba8626 greybus: operatio... |
919 920 |
const struct gb_operation_msg_hdr *header, void *data, size_t size) |
d37b1db13 greybus: refactor... |
921 922 923 |
{ struct gb_operation *operation; struct gb_message *message; |
7cfa69955 greybus: only rec... |
924 |
size_t message_size; |
dfcba8626 greybus: operatio... |
925 926 927 928 |
u16 operation_id; int errno; operation_id = le16_to_cpu(header->operation_id); |
d37b1db13 greybus: refactor... |
929 |
|
3e2ee2c1e greybus: operatio... |
930 |
if (!operation_id) { |
b0e97bce1 greybus: operatio... |
931 |
dev_err_ratelimited(&connection->hd->dev, |
8478c35a8 staging: greybus:... |
932 933 934 |
"%s: invalid response id 0 received ", connection->name); |
3e2ee2c1e greybus: operatio... |
935 936 |
return; } |
048a7ffe2 greybus: operatio... |
937 |
operation = gb_operation_find_outgoing(connection, operation_id); |
d37b1db13 greybus: refactor... |
938 |
if (!operation) { |
b0e97bce1 greybus: operatio... |
939 |
dev_err_ratelimited(&connection->hd->dev, |
8478c35a8 staging: greybus:... |
940 941 942 |
"%s: unexpected response id 0x%04x received ", connection->name, operation_id); |
d37b1db13 greybus: refactor... |
943 944 |
return; } |
dfcba8626 greybus: operatio... |
945 |
errno = gb_operation_status_map(header->result); |
c08b1ddae greybus: dynamica... |
946 |
message = operation->response; |
34804efb0 greybus: operatio... |
947 |
message_size = sizeof(*header) + message->payload_size; |
7e43e337a greybus: operatio... |
948 |
if (!errno && size > message_size) { |
b0e97bce1 greybus: operatio... |
949 |
dev_err_ratelimited(&connection->hd->dev, |
8478c35a8 staging: greybus:... |
950 951 952 953 |
"%s: malformed response 0x%02x received (%zu > %zu) ", connection->name, header->type, size, message_size); |
64ce39a34 greybus: pass res... |
954 |
errno = -EMSGSIZE; |
7e43e337a greybus: operatio... |
955 956 957 958 |
} else if (!errno && size < message_size) { if (gb_operation_short_response_allowed(operation)) { message->payload_size = size - sizeof(*header); } else { |
b0e97bce1 greybus: operatio... |
959 |
dev_err_ratelimited(&connection->hd->dev, |
8478c35a8 staging: greybus:... |
960 961 962 963 |
"%s: short response 0x%02x received (%zu < %zu) ", connection->name, header->type, size, message_size); |
7e43e337a greybus: operatio... |
964 965 |
errno = -EMSGSIZE; } |
d37b1db13 greybus: refactor... |
966 |
} |
d37b1db13 greybus: refactor... |
967 |
|
25d0f81a0 greybus: remove s... |
968 |
/* We must ignore the payload if a bad status is returned */ |
64ce39a34 greybus: pass res... |
969 |
if (errno) |
34804efb0 greybus: operatio... |
970 |
size = sizeof(*header); |
d37b1db13 greybus: refactor... |
971 972 |
/* The rest will be handled in work queue context */ |
e4340b13f greybus: operatio... |
973 |
if (gb_operation_result_set(operation, errno)) { |
dfcba8626 greybus: operatio... |
974 |
memcpy(message->buffer, data, size); |
112f563e1 greybus: operatio... |
975 976 |
trace_gb_message_recv_response(message); |
701615f82 greybus: operatio... |
977 |
queue_work(gb_operation_completion_wq, &operation->work); |
e4340b13f greybus: operatio... |
978 |
} |
0581f28ef greybus: operatio... |
979 980 |
gb_operation_put(operation); |
d37b1db13 greybus: refactor... |
981 982 983 984 985 986 |
} /* * Handle data arriving on a connection. As soon as we return the * supplied data buffer will be reused (so unless we do something * with, it's effectively dropped). |
2eb585f8d greybus: move rec... |
987 |
*/ |
61089e89e greybus: rework r... |
988 |
void gb_connection_recv(struct gb_connection *connection, |
8478c35a8 staging: greybus:... |
989 |
void *data, size_t size) |
d90c25b0a greybus: let oper... |
990 |
{ |
564c72b1c greybus: operatio... |
991 |
struct gb_operation_msg_hdr header; |
25cdd7aa5 greybus: operatio... |
992 |
struct device *dev = &connection->hd->dev; |
d37b1db13 greybus: refactor... |
993 |
size_t msg_size; |
d90c25b0a greybus: let oper... |
994 |
|
8890f9571 greybus: operatio... |
995 |
if (connection->state == GB_CONNECTION_STATE_DISABLED || |
8478c35a8 staging: greybus:... |
996 |
gb_connection_is_offloaded(connection)) { |
b0e97bce1 greybus: operatio... |
997 998 |
dev_warn_ratelimited(dev, "%s: dropping %zu received bytes ", |
8478c35a8 staging: greybus:... |
999 |
connection->name, size); |
36561f23a greybus: define c... |
1000 |
return; |
d37b1db13 greybus: refactor... |
1001 |
} |
36561f23a greybus: define c... |
1002 |
|
564c72b1c greybus: operatio... |
1003 |
if (size < sizeof(header)) { |
b0e97bce1 greybus: operatio... |
1004 1005 |
dev_err_ratelimited(dev, "%s: short message received ", |
8478c35a8 staging: greybus:... |
1006 |
connection->name); |
d90c25b0a greybus: let oper... |
1007 1008 |
return; } |
564c72b1c greybus: operatio... |
1009 1010 1011 |
/* Use memcpy as data may be unaligned */ memcpy(&header, data, sizeof(header)); msg_size = le16_to_cpu(header.size); |
0150bd7f2 greybus: operatio... |
1012 |
if (size < msg_size) { |
b0e97bce1 greybus: operatio... |
1013 |
dev_err_ratelimited(dev, |
8478c35a8 staging: greybus:... |
1014 1015 1016 1017 1018 |
"%s: incomplete message 0x%04x of type 0x%02x received (%zu < %zu) ", connection->name, le16_to_cpu(header.operation_id), header.type, size, msg_size); |
d37b1db13 greybus: refactor... |
1019 |
return; /* XXX Should still complete operation */ |
d90c25b0a greybus: let oper... |
1020 |
} |
2321f049a greybus: operatio... |
1021 |
if (header.type & GB_MESSAGE_TYPE_RESPONSE) { |
dfcba8626 greybus: operatio... |
1022 |
gb_connection_recv_response(connection, &header, data, |
8478c35a8 staging: greybus:... |
1023 |
msg_size); |
2321f049a greybus: operatio... |
1024 1025 |
} else { gb_connection_recv_request(connection, &header, data, |
8478c35a8 staging: greybus:... |
1026 |
msg_size); |
2321f049a greybus: operatio... |
1027 |
} |
2eb585f8d greybus: move rec... |
1028 |
} |
e1158df06 greybus: define o... |
1029 |
/* |
5a3be769e greybus: operatio... |
1030 1031 |
* Cancel an outgoing operation synchronously, and record the given error to * indicate why. |
e1158df06 greybus: define o... |
1032 |
*/ |
f68c05c02 greybus: cancel o... |
1033 |
void gb_operation_cancel(struct gb_operation *operation, int errno) |
e1158df06 greybus: define o... |
1034 |
{ |
5a3be769e greybus: operatio... |
1035 1036 1037 1038 1039 |
if (WARN_ON(gb_operation_is_incoming(operation))) return; if (gb_operation_result_set(operation, errno)) { gb_message_cancel(operation->request); |
701615f82 greybus: operatio... |
1040 |
queue_work(gb_operation_completion_wq, &operation->work); |
abe9a3006 greybus: first op... |
1041 |
} |
5c8ad599b greybus: operatio... |
1042 |
trace_gb_message_cancel_outgoing(operation->request); |
fd7134a3c greybus: operatio... |
1043 1044 1045 |
atomic_inc(&operation->waiters); wait_event(gb_operation_cancellation_queue, |
8478c35a8 staging: greybus:... |
1046 |
!gb_operation_is_active(operation)); |
fd7134a3c greybus: operatio... |
1047 |
atomic_dec(&operation->waiters); |
e1158df06 greybus: define o... |
1048 |
} |
1dad6b351 greybus: operatio... |
1049 |
EXPORT_SYMBOL_GPL(gb_operation_cancel); |
e1158df06 greybus: define o... |
1050 |
|
5a3be769e greybus: operatio... |
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 |
/* * Cancel an incoming operation synchronously. Called during connection tear * down. */ void gb_operation_cancel_incoming(struct gb_operation *operation, int errno) { if (WARN_ON(!gb_operation_is_incoming(operation))) return; if (!gb_operation_is_unidirectional(operation)) { /* * Make sure the request handler has submitted the response * before cancelling it. */ flush_work(&operation->work); if (!gb_operation_result_set(operation, errno)) gb_message_cancel(operation->response); } |
5c8ad599b greybus: operatio... |
1069 |
trace_gb_message_cancel_incoming(operation->response); |
5a3be769e greybus: operatio... |
1070 1071 1072 |
atomic_inc(&operation->waiters); wait_event(gb_operation_cancellation_queue, |
8478c35a8 staging: greybus:... |
1073 |
!gb_operation_is_active(operation)); |
5a3be769e greybus: operatio... |
1074 1075 |
atomic_dec(&operation->waiters); } |
10aa801d3 greybus: operatio... |
1076 |
/** |
410abddb0 greybus: fix outd... |
1077 |
* gb_operation_sync_timeout() - implement a "simple" synchronous operation |
10aa801d3 greybus: operatio... |
1078 1079 1080 1081 1082 1083 |
* @connection: the Greybus connection to send this to * @type: the type of operation to send * @request: pointer to a memory buffer to copy the request from * @request_size: size of @request * @response: pointer to a memory buffer to copy the response to * @response_size: the size of @response. |
129a06f54 greybus: operatio... |
1084 |
* @timeout: operation timeout in milliseconds |
10aa801d3 greybus: operatio... |
1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 |
* * This function implements a simple synchronous Greybus operation. It sends * the provided operation request and waits (sleeps) until the corresponding * operation response message has been successfully received, or an error * occurs. @request and @response are buffers to hold the request and response * data respectively, and if they are not NULL, their size must be specified in * @request_size and @response_size. * * If a response payload is to come back, and @response is not NULL, * @response_size number of bytes will be copied into @response if the operation * is successful. * * If there is an error, the response buffer is left alone. */ |
129a06f54 greybus: operatio... |
1099 |
int gb_operation_sync_timeout(struct gb_connection *connection, int type, |
8478c35a8 staging: greybus:... |
1100 1101 1102 |
void *request, int request_size, void *response, int response_size, unsigned int timeout) |
10aa801d3 greybus: operatio... |
1103 1104 1105 1106 1107 1108 1109 1110 1111 |
{ struct gb_operation *operation; int ret; if ((response_size && !response) || (request_size && !request)) return -EINVAL; operation = gb_operation_create(connection, type, |
e420721b4 greybus: operatio... |
1112 1113 |
request_size, response_size, GFP_KERNEL); |
10aa801d3 greybus: operatio... |
1114 1115 1116 1117 |
if (!operation) return -ENOMEM; if (request_size) |
6cd6ec55f greybus: fix a bu... |
1118 |
memcpy(operation->request->payload, request, request_size); |
10aa801d3 greybus: operatio... |
1119 |
|
129a06f54 greybus: operatio... |
1120 |
ret = gb_operation_request_send_sync_timeout(operation, timeout); |
ee8f81b09 greybus: operatio... |
1121 |
if (ret) { |
05e309556 greybus: Revert "... |
1122 |
dev_err(&connection->hd->dev, |
e514dec73 greybus: operatio... |
1123 1124 1125 |
"%s: synchronous operation id 0x%04x of type 0x%02x failed: %d ", connection->name, operation->id, type, ret); |
ee8f81b09 greybus: operatio... |
1126 1127 |
} else { if (response_size) { |
10aa801d3 greybus: operatio... |
1128 1129 |
memcpy(response, operation->response->payload, response_size); |
ee8f81b09 greybus: operatio... |
1130 1131 |
} } |
6ab1ce4d5 greybus: operatio... |
1132 1133 |
gb_operation_put(operation); |
10aa801d3 greybus: operatio... |
1134 1135 1136 |
return ret; } |
129a06f54 greybus: operatio... |
1137 |
EXPORT_SYMBOL_GPL(gb_operation_sync_timeout); |
10aa801d3 greybus: operatio... |
1138 |
|
5fdc027d5 greybus: operatio... |
1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 |
/** * gb_operation_unidirectional_timeout() - initiate a unidirectional operation * @connection: connection to use * @type: type of operation to send * @request: memory buffer to copy the request from * @request_size: size of @request * @timeout: send timeout in milliseconds * * Initiate a unidirectional operation by sending a request message and * waiting for it to be acknowledged as sent by the host device. * * Note that successful send of a unidirectional operation does not imply that * the request as actually reached the remote end of the connection. */ int gb_operation_unidirectional_timeout(struct gb_connection *connection, |
8478c35a8 staging: greybus:... |
1154 1155 1156 |
int type, void *request, int request_size, unsigned int timeout) |
5fdc027d5 greybus: operatio... |
1157 1158 1159 1160 1161 1162 1163 1164 |
{ struct gb_operation *operation; int ret; if (request_size && !request) return -EINVAL; operation = gb_operation_create_flags(connection, type, |
8478c35a8 staging: greybus:... |
1165 1166 1167 |
request_size, 0, GB_OPERATION_FLAG_UNIDIRECTIONAL, GFP_KERNEL); |
5fdc027d5 greybus: operatio... |
1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 |
if (!operation) return -ENOMEM; if (request_size) memcpy(operation->request->payload, request, request_size); ret = gb_operation_request_send_sync_timeout(operation, timeout); if (ret) { dev_err(&connection->hd->dev, "%s: unidirectional operation of type 0x%02x failed: %d ", connection->name, type, ret); } gb_operation_put(operation); return ret; } EXPORT_SYMBOL_GPL(gb_operation_unidirectional_timeout); |
47ed2c924 greybus: tag core... |
1187 |
int __init gb_operation_init(void) |
2eb585f8d greybus: move rec... |
1188 |
{ |
1e5613b4a greybus: operatio... |
1189 |
gb_message_cache = kmem_cache_create("gb_message_cache", |
8478c35a8 staging: greybus:... |
1190 1191 |
sizeof(struct gb_message), 0, 0, NULL); |
1e5613b4a greybus: operatio... |
1192 |
if (!gb_message_cache) |
0cffcac30 greybus: create a... |
1193 |
return -ENOMEM; |
5b3db0dda greybus: create a... |
1194 |
gb_operation_cache = kmem_cache_create("gb_operation_cache", |
8478c35a8 staging: greybus:... |
1195 1196 |
sizeof(struct gb_operation), 0, 0, NULL); |
5b3db0dda greybus: create a... |
1197 |
if (!gb_operation_cache) |
1e5613b4a greybus: operatio... |
1198 |
goto err_destroy_message_cache; |
5b3db0dda greybus: create a... |
1199 |
|
701615f82 greybus: operatio... |
1200 |
gb_operation_completion_wq = alloc_workqueue("greybus_completion", |
8478c35a8 staging: greybus:... |
1201 |
0, 0); |
701615f82 greybus: operatio... |
1202 1203 |
if (!gb_operation_completion_wq) goto err_destroy_operation_cache; |
2eb585f8d greybus: move rec... |
1204 |
return 0; |
5a5bc354c greybus: operatio... |
1205 |
|
701615f82 greybus: operatio... |
1206 1207 1208 |
err_destroy_operation_cache: kmem_cache_destroy(gb_operation_cache); gb_operation_cache = NULL; |
1e5613b4a greybus: operatio... |
1209 1210 1211 |
err_destroy_message_cache: kmem_cache_destroy(gb_message_cache); gb_message_cache = NULL; |
0cffcac30 greybus: create a... |
1212 1213 |
return -ENOMEM; |
2eb585f8d greybus: move rec... |
1214 |
} |
f35ab903e greybus: endo: de... |
1215 |
void gb_operation_exit(void) |
2eb585f8d greybus: move rec... |
1216 |
{ |
701615f82 greybus: operatio... |
1217 1218 |
destroy_workqueue(gb_operation_completion_wq); gb_operation_completion_wq = NULL; |
837b3b7c0 greybus: operatio... |
1219 1220 |
kmem_cache_destroy(gb_operation_cache); gb_operation_cache = NULL; |
1e5613b4a greybus: operatio... |
1221 1222 |
kmem_cache_destroy(gb_message_cache); gb_message_cache = NULL; |
d90c25b0a greybus: let oper... |
1223 |
} |