greybus: move receive handling to operation layer

Create a work queue to do the bulk of processing of received
operation request or response messages.

Signed-off-by: Alex Elder <elder@linaro.org>
Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
This commit is contained in:
Alex Elder 2014-10-16 06:35:34 -05:00 committed by Greg Kroah-Hartman
parent d90c25b0a2
commit 2eb585f8df
3 changed files with 125 additions and 37 deletions

View File

@ -284,6 +284,12 @@ static int __init gb_init(void)
goto error_gbuf;
}
retval = gb_operation_init();
if (retval) {
pr_err("gb_operation_init failed\n");
goto error_operation;
}
retval = gb_tty_init();
if (retval) {
pr_err("gb_tty_init failed\n");
@ -293,6 +299,9 @@ static int __init gb_init(void)
return 0;
error_tty:
gb_operation_exit();
error_operation:
gb_gbuf_exit();
error_gbuf:
@ -310,6 +319,7 @@ static int __init gb_init(void)
static void __exit gb_exit(void)
{
gb_tty_exit();
gb_operation_exit();
gb_gbuf_exit();
gb_ap_exit();
bus_unregister(&greybus_bus_type);

View File

@ -24,6 +24,9 @@
*/
#define GB_OPERATION_MESSAGE_SIZE_MAX 4096
/* Workqueue to handle Greybus operation completions. */
static struct workqueue_struct *gb_operation_recv_workqueue;
/*
* All operation messages (both requests and responses) begin with
* a common header that encodes the size of the data (header
@ -126,16 +129,13 @@ gb_operation_find(struct gb_connection *connection, u16 id)
*/
void gb_operation_complete(struct gb_operation *operation)
{
/* XXX Should probably report bad status if no callback */
if (operation->callback)
operation->callback(operation);
else
complete_all(&operation->completion);
gb_operation_destroy(operation);
}
/*
* Wait for a submitted operatnoi to complete */
/* Wait for a submitted operation to complete */
int gb_operation_wait(struct gb_operation *operation)
{
int ret;
@ -148,46 +148,100 @@ int gb_operation_wait(struct gb_operation *operation)
}
typedef void (*gb_operation_recv_handler)(struct gb_operation *operation);
static gb_operation_recv_handler gb_operation_recv_handlers[] = {
[GREYBUS_PROTOCOL_CONTROL] = NULL,
[GREYBUS_PROTOCOL_AP] = NULL,
[GREYBUS_PROTOCOL_GPIO] = NULL,
[GREYBUS_PROTOCOL_I2C] = NULL,
[GREYBUS_PROTOCOL_UART] = NULL,
[GREYBUS_PROTOCOL_HID] = NULL,
[GREYBUS_PROTOCOL_VENDOR] = NULL,
};
static void gb_operation_request_handle(struct gb_operation *operation)
{
u8 protocol = operation->connection->protocol;
/* Subtract one from array size to stay within u8 range */
if (protocol <= (u8)(ARRAY_SIZE(gb_operation_recv_handlers) - 1)) {
gb_operation_recv_handler handler;
handler = gb_operation_recv_handlers[protocol];
if (handler) {
handler(operation); /* Handle the request */
return;
}
}
gb_connection_err(operation->connection, "unrecognized protocol %u\n",
(unsigned int)protocol);
operation->result = GB_OP_PROTOCOL_BAD;
gb_operation_complete(operation);
}
/*
* Called when an operation buffer completes.
* Either this operation contains an incoming request, or its
* response has arrived. An incoming request will have a null
* response buffer pointer (it is the responsibility of the request
* handler to allocate and fill in the response buffer).
*/
static void gb_operation_recv_work(struct work_struct *recv_work)
{
struct gb_operation *operation;
bool incoming_request;
operation = container_of(recv_work, struct gb_operation, recv_work);
incoming_request = operation->response == NULL;
if (incoming_request)
gb_operation_request_handle(operation);
gb_operation_complete(operation);
/* We're finished with the buffer we read into */
if (incoming_request)
greybus_gbuf_finished(operation->request);
else
greybus_gbuf_finished(operation->response);
}
/*
* Buffer completion function. We get notified whenever any buffer
* completes. For outbound messages, this tells us that the message
* has been sent. For inbound messages, it means the data has
* landed in the buffer and is ready to be processed.
*
* Either way, we don't do anything. We don't really care when an
* outbound message has been sent, and for incoming messages we
* we'll be done with everything we need to do before we mark it
* finished.
*
* XXX We may want to record that a buffer is (or is no longer) in flight.
*/
static void gb_operation_gbuf_complete(struct gbuf *gbuf)
{
/* Don't do anything */
struct gb_operation *operation;
struct gb_operation_msg_hdr *header;
u16 id;
/*
* This isn't right, but it keeps things balanced until we
* can set up operation response handling.
*/
header = gbuf->transfer_buffer;
id = le16_to_cpu(header->id);
operation = gb_operation_find(gbuf->connection, id);
if (operation)
gb_operation_remove(operation);
else
gb_connection_err(gbuf->connection, "operation not found");
return;
}
/*
* Allocate a buffer to be used for an operation request or response
* message. Both types of message contain a header, which is filled
* in here. W
* message. For outgoing messages, both types of message contain a
* common header, which is filled in here. Incoming requests or
* responses also contain the same header, but there's no need to
* initialize it here (it'll be overwritten by the incoming
* message).
*/
struct gbuf *gb_operation_gbuf_create(struct gb_operation *operation,
u8 type, size_t size, bool outbound)
u8 type, size_t size, bool data_out)
{
struct gb_connection *connection = operation->connection;
struct gb_operation_msg_hdr *header;
struct gbuf *gbuf;
gfp_t gfp_flags = outbound ? GFP_KERNEL : GFP_ATOMIC;
gfp_t gfp_flags = data_out ? GFP_KERNEL : GFP_ATOMIC;
/* Operation buffers hold a header in addition to their payload */
size += sizeof(*header);
gbuf = greybus_alloc_gbuf(connection, gb_operation_gbuf_complete,
size, outbound, gfp_flags, operation);
size, data_out, gfp_flags, operation);
if (!gbuf)
return NULL;
@ -222,11 +276,7 @@ struct gb_operation *gb_operation_create(struct gb_connection *connection,
{
struct gb_operation *operation;
gfp_t gfp_flags = response_size ? GFP_KERNEL : GFP_ATOMIC;
if (!request_size) {
gb_connection_err(connection, "zero-sized request");
return NULL;
}
bool outgoing = response_size != 0;
/* XXX Use a slab cache */
operation = kzalloc(sizeof(*operation), gfp_flags);
@ -235,7 +285,8 @@ struct gb_operation *gb_operation_create(struct gb_connection *connection,
operation->connection = connection; /* XXX refcount? */
operation->request = gb_operation_gbuf_create(operation, type,
request_size, true);
request_size,
outgoing);
if (!operation->request) {
kfree(operation);
return NULL;
@ -245,10 +296,11 @@ struct gb_operation *gb_operation_create(struct gb_connection *connection,
/* We always use the full request buffer */
operation->request->actual_length = request_size;
if (response_size) {
if (outgoing) {
type |= GB_OPERATION_TYPE_RESPONSE;
operation->response = gb_operation_gbuf_create(operation,
type, response_size, false);
type, response_size,
false);
if (!operation->response) {
greybus_free_gbuf(operation->request);
kfree(operation);
@ -259,6 +311,7 @@ struct gb_operation *gb_operation_create(struct gb_connection *connection,
sizeof(struct gb_operation_msg_hdr);
}
INIT_WORK(&operation->recv_work, gb_operation_recv_work);
operation->callback = NULL; /* set at submit time */
init_completion(&operation->completion);
@ -333,6 +386,11 @@ int gb_operation_response_send(struct gb_operation *operation)
return 0;
}
/*
* Handle data arriving on a connection. This is called in
* interrupt context, so just copy the incoming data into a buffer
* and do remaining handling via a work queue.
*/
void gb_connection_operation_recv(struct gb_connection *connection,
void *data, size_t size)
{
@ -354,7 +412,7 @@ void gb_connection_operation_recv(struct gb_connection *connection,
operation = gb_operation_find(connection, id);
if (!operation) {
gb_connection_err(connection, "operation not found");
return;
return;
}
gb_operation_remove(operation);
gbuf = operation->response;
@ -376,5 +434,20 @@ void gb_connection_operation_recv(struct gb_connection *connection,
memcpy(gbuf->transfer_buffer, data, msg_size);
gbuf->actual_length = msg_size;
/* XXX And now we let a work queue handle the rest */
/* The rest will be handled in work queue context */
queue_work(gb_operation_recv_workqueue, &operation->recv_work);
}
int gb_operation_init(void)
{
gb_operation_recv_workqueue = alloc_workqueue("greybus_recv", 0, 1);
if (!gb_operation_recv_workqueue)
return -ENOMEM;
return 0;
}
void gb_operation_exit(void)
{
destroy_workqueue(gb_operation_recv_workqueue);
}

View File

@ -16,6 +16,7 @@ enum gb_operation_status {
GB_OP_INVALID = 1,
GB_OP_NO_MEMORY = 2,
GB_OP_INTERRUPTED = 3,
GB_OP_PROTOCOL_BAD = 4,
};
/*
@ -55,6 +56,7 @@ struct gb_operation {
u16 id;
u8 result;
struct work_struct recv_work;
gb_operation_callback callback; /* If asynchronous */
struct completion completion; /* Used if no callback */
@ -81,4 +83,7 @@ int gb_operation_response_send(struct gb_operation *operation);
int gb_operation_wait(struct gb_operation *operation);
void gb_operation_complete(struct gb_operation *operation);
int gb_operation_init(void);
void gb_operation_exit(void);
#endif /* !__OPERATION_H */