diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index ed39a5c6b6fd..d6c4cc3c89ff 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -7,6 +7,7 @@ greybus-y := core.o \ interface.o \ function.o \ connection.o \ + operation.o \ i2c-gb.o \ gpio-gb.o \ sdio-gb.o \ diff --git a/drivers/staging/greybus/connection.c b/drivers/staging/greybus/connection.c index 113c98542858..fa5ab5d30ae7 100644 --- a/drivers/staging/greybus/connection.c +++ b/drivers/staging/greybus/connection.c @@ -6,6 +6,9 @@ * Released under the GPLv2 only. */ +#include + +#include "kernel_ver.h" #include "greybus.h" /* @@ -13,6 +16,9 @@ * between a CPort on a (local) Greybus host device and a CPort on * another Greybus module. * + * A connection also maintains the state of operations sent over the + * connection. + * * Returns a pointer to the new connection if successful, or a null * pointer otherwise. */ @@ -28,6 +34,8 @@ struct gb_connection *gb_connection_create(struct greybus_host_device *hd, connection->hd = hd; /* XXX refcount? */ connection->cport_id = cport_id; connection->function = function; /* XXX refcount? */ + INIT_LIST_HEAD(&connection->operations); + atomic_set(&connection->op_cycle, 0); return connection; } @@ -41,8 +49,14 @@ void gb_connection_destroy(struct gb_connection *connection) return; /* XXX Need to wait for any outstanding requests to complete */ + WARN_ON(!list_empty(&connection->operations)); /* kref_put(function); */ /* kref_put(hd); */ kfree(connection); } + +u16 gb_connection_op_id(struct gb_connection *connection) +{ + return (u16)(atomic_inc_return(&connection->op_cycle) % U16_MAX); +} diff --git a/drivers/staging/greybus/connection.h b/drivers/staging/greybus/connection.h index 79b3b07f94c4..c653c95d2834 100644 --- a/drivers/staging/greybus/connection.h +++ b/drivers/staging/greybus/connection.h @@ -20,10 +20,15 @@ struct gb_connection { u16 cport_id; /* Host side */ struct list_head host_links; + + struct list_head operations; + atomic_t op_cycle; }; bool gb_connection_setup(struct greybus_host_device *hd, u16 cport_id, struct gb_function *function); void gb_connection_teardown(struct gb_connection *connection); +u16 gb_connection_op_id(struct gb_connection *connection); + #endif /* __CONNECTION_H */ diff --git a/drivers/staging/greybus/greybus.h b/drivers/staging/greybus/greybus.h index 732cc5e51dc4..9a66fd1e60b0 100644 --- a/drivers/staging/greybus/greybus.h +++ b/drivers/staging/greybus/greybus.h @@ -24,6 +24,7 @@ #include "interface.h" #include "function.h" #include "connection.h" +#include "operation.h" /* Matches up with the Greybus Protocol specification document */ diff --git a/drivers/staging/greybus/kernel_ver.h b/drivers/staging/greybus/kernel_ver.h index 4aa5b83bff60..c9ea7a94f4e6 100644 --- a/drivers/staging/greybus/kernel_ver.h +++ b/drivers/staging/greybus/kernel_ver.h @@ -22,4 +22,8 @@ #define U8_MAX ((u8)~0U) #endif /* ! U8_MAX */ +#ifndef U16_MAX +#define U16_MAX ((u16)(~0U)) +#endif /* !U16_MAX */ + #endif /* __GREYBUS_KERNEL_VER_H */ diff --git a/drivers/staging/greybus/operation.c b/drivers/staging/greybus/operation.c new file mode 100644 index 000000000000..5cb23aa3867a --- /dev/null +++ b/drivers/staging/greybus/operation.c @@ -0,0 +1,171 @@ +/* + * Greybus operations + * + * Copyright 2014 Google Inc. + * + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include + +#include "greybus.h" + +/* + * All operation messages (both requests and responses) begin with + * a common header that encodes the size of the data (header + * included). This header also contains a unique identifier, which + * is used to keep track of in-flight operations. Finally, the + * header contains a operation type field, whose interpretation is + * dependent on what type of device lies on the other end of the + * connection. Response messages are distinguished from request + * messages by setting the high bit (0x80) in the operation type + * value. + * + * The wire format for all numeric fields in the header is little + * endian. Any operation-specific data begins immediately after the + * header, and is 64-bit aligned. + */ +struct gb_operation_msg_hdr { + __le16 size; /* Size in bytes of header + payload */ + __le16 id; /* Operation unique id */ + __u8 type; /* E.g GB_I2C_TYPE_* or GB_GPIO_TYPE_* */ + /* 3 bytes pad, must be zero (ignore when read) */ +} __aligned(sizeof(u64)); + +/* XXX Could be per-host device, per-module, or even per-connection */ +static DEFINE_SPINLOCK(gb_operations_lock); + +/* + * An operations's response message has arrived. If no callback was + * supplied it was submitted for asynchronous completion, so we notify + * any waiters. Otherwise we assume calling the completion is enough + * and nobody else will be waiting. + */ +void gb_operation_complete(struct gb_operation *operation) +{ + if (operation->callback) + operation->callback(operation); + else + complete_all(&operation->completion); +} + +/* + * Wait for a submitted operatnoi to complete */ +int gb_operation_wait(struct gb_operation *operation) +{ + int ret; + + ret = wait_for_completion_interruptible(&operation->completion); + /* If interrupted, cancel the in-flight buffer */ + if (ret < 0) + ret = greybus_kill_gbuf(operation->gbuf); + return ret; + +} + +/* + * Submit an outbound operation. The caller has filled in any + * payload so the request message is ready to go. If non-null, + * the callback function supplied will be called when the response + * message has arrived indicating the operation is complete. A null + * callback function is used for a synchronous request; return from + * this function won't occur until the operation is complete (or an + * interrupt occurs). + */ +int gb_operation_submit(struct gb_operation *operation, + gb_operation_callback callback) +{ + int ret; + + /* XXX + * gfp is probably GFP_ATOMIC but really I think + * the gfp mask should go away. + */ + operation->callback = callback; + ret = greybus_submit_gbuf(operation->gbuf, GFP_KERNEL); + if (ret) + return ret; + if (!callback) + ret = gb_operation_wait(operation); + + return ret; +} + +/* + * Called when a greybus request message has actually been sent. + */ +static void gbuf_out_callback(struct gbuf *gbuf) +{ + /* Record it's been submitted; need response now */ +} + +/* + * Create a Greybus operation having a buffer big enough for an + * outgoing payload of the given size to be sent over the given + * connection. + * + * Returns a pointer to the new operation or a null pointer if a + * failure occurs due to memory exhaustion. + */ +struct gb_operation *gb_operation_create(struct gb_connection *connection, + size_t size) +{ + struct gb_operation *operation; + struct gb_operation_msg_hdr *header; + struct gbuf *gbuf; + + /* XXX Use a slab cache */ + operation = kzalloc(sizeof(*operation), GFP_KERNEL); + if (!operation) + return NULL; + + /* Our buffer holds a header in addition to the requested payload */ + size += sizeof(*header); + gbuf = greybus_alloc_gbuf(connection->function->interface->gmod, + connection->cport_id, + gbuf_out_callback, size, + GFP_KERNEL, operation); + if (gbuf) { + kfree(operation); + return NULL; + } + + operation->connection = connection; /* XXX refcount? */ + + /* Fill in the header structure and payload pointer */ + operation->gbuf = gbuf; + header = (struct gb_operation_msg_hdr *)&gbuf->transfer_buffer; + header->id = 0; + header->size = size; + operation->payload = (char *)header + sizeof(*header); + + operation->callback = NULL; /* set at submit time */ + init_completion(&operation->completion); + + spin_lock_irq(&gb_operations_lock); + list_add_tail(&operation->links, &connection->operations); + spin_unlock_irq(&gb_operations_lock); + + return operation; +} + +/* + * Destroy a previously created operation. + */ +void gb_operation_destroy(struct gb_operation *operation) +{ + if (WARN_ON(!operation)) + return; + + /* XXX Make sure it's not in flight */ + spin_lock_irq(&gb_operations_lock); + list_del(&operation->links); + spin_unlock_irq(&gb_operations_lock); + + greybus_free_gbuf(operation->gbuf); + + kfree(operation); +} diff --git a/drivers/staging/greybus/operation.h b/drivers/staging/greybus/operation.h new file mode 100644 index 000000000000..96a7a0fcba56 --- /dev/null +++ b/drivers/staging/greybus/operation.h @@ -0,0 +1,69 @@ +/* + * Greybus operations + * + * Copyright 2014 Google Inc. + * + * Released under the GPLv2 only. + */ + +#ifndef __OPERATION_H +#define __OPERATION_H + +#include + +enum gb_operation_status { + GB_OP_SUCCESS = 0, + GB_OP_INVALID = 1, + GB_OP_NO_MEMORY = 2, + GB_OP_INTERRUPTED = 3, +}; + +/* + * A Greybus operation is a remote procedure call performed over a + * connection between the AP and a function on Greybus module. + * Every operation consists of a request message sent to the other + * end of the connection coupled with a reply returned to the + * sender. + * + * The state for managing active requests on a connection is held in + * the connection structure. + * + * YADA YADA + * + * submitting each request and providing its matching response to + * the caller when it arrives. Operations normally complete + * asynchronously, and when an operation's response arrives its + * callback function is executed. The callback pointer is supplied + * at the time the operation is submitted; a null callback pointer + * causes synchronous operation--the caller is blocked until + * the response arrives. In addition, it is possible to await + * the completion of a submitted asynchronous operation. + * + * A Greybus device operation includes a Greybus buffer to hold the + * data sent to the device. The only field within a Greybus + * operation that should be used by a caller is the payload pointer, + * which should be used to populate the request data. This pointer + * is guaranteed to be 64-bit aligned. + * XXX and callback? + */ +struct gb_operation; +typedef void (*gb_operation_callback)(struct gb_operation *); +struct gb_operation { + struct gb_connection *connection; + struct gbuf *gbuf; + void *payload; /* sender data */ + gb_operation_callback callback; /* If asynchronous */ + struct completion completion; /* Used if no callback */ + u8 result; + + struct list_head links; /* connection->operations */ +}; + +struct gb_operation *gb_operation_create(struct gb_connection *connection, + size_t size); +void gb_operation_destroy(struct gb_operation *operation); + +int gb_operation_wait(struct gb_operation *operation); +void gb_operation_complete(struct gb_operation *operation); + +#endif /* !__OPERATION_H */