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>
diff --git a/drivers/staging/greybus/operation.c b/drivers/staging/greybus/operation.c
index 092ceb6..7753bf7 100644
--- a/drivers/staging/greybus/operation.c
+++ b/drivers/staging/greybus/operation.c
@@ -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 @@
*/
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 @@
}
+
+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 *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 @@
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 @@
/* 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 @@
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 @@
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 @@
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 @@
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);
}