]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
greybus: introduce an operation abstraction
authorAlex Elder <elder@linaro.org>
Thu, 2 Oct 2014 02:54:15 +0000 (21:54 -0500)
committerGreg Kroah-Hartman <greg@kroah.com>
Fri, 3 Oct 2014 04:18:41 +0000 (21:18 -0700)
This patch defines a new "operation" abstraction.  An operation is a
request from by one end of a connection to the function (or AP) on
the other, coupled with a matching response returned to the requestor.
The request indicates some action to be performed by the target of
the request (such as "read some data").  Once the action has
completed the target sends back an operation response message.
Additional data can be supplied by the sender with its request,
and/or by the target with its resposne message.

Each request message has a unique id, generated by the sender.
The sender recognizes the matching response by the presence
of this id value.  Each end of a connection is responsible
for creating unique ids for the requests it sends.

An operation also has a type, whose interpretation is dependent on
the function type on the end of the connection opposite the sender.
It is up to the creator of an operation to fill in the data (if any)
to be sent with the request.

Note that not all requests are initiated by the AP.  Incoming data
on a module function can result in a request message being sent from
that function to the AP to notify of the data's arrival.  Once the
AP has processed this, it sends a response to the sender.

Every operation response contains a status byte.  If it's value
is 0, the operation was successful.  Any other value indicates
an error.

Add a defintion of U16_MAX to "kernel_ver.h".

Signed-off-by: Alex Elder <elder@linaro.org>
Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
drivers/staging/greybus/Makefile
drivers/staging/greybus/connection.c
drivers/staging/greybus/connection.h
drivers/staging/greybus/greybus.h
drivers/staging/greybus/kernel_ver.h
drivers/staging/greybus/operation.c [new file with mode: 0644]
drivers/staging/greybus/operation.h [new file with mode: 0644]

index ed39a5c6b6fd0ef2af928cd52b7c8091952f307a..d6c4cc3c89ff1cc6fa427920b86bd6cefd4b2364 100644 (file)
@@ -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       \
index 113c98542858a6c0c9e7e11bb81b8d80f6799b10..fa5ab5d30ae74248568016f8a6d6c1b9feb35a39 100644 (file)
@@ -6,6 +6,9 @@
  * Released under the GPLv2 only.
  */
 
+#include <linux/atomic.h>
+
+#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);
+}
index 79b3b07f94c4350c623b4045555c92575e51a125..c653c95d2834d144a85ecfb02831b9384a6a04bd 100644 (file)
@@ -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 */
index 732cc5e51dc4732b4a4937514f65de13fa11f263..9a66fd1e60b09c45ddd45b135400f2de5f687218 100644 (file)
@@ -24,6 +24,7 @@
 #include "interface.h"
 #include "function.h"
 #include "connection.h"
+#include "operation.h"
 
 
 /* Matches up with the Greybus Protocol specification document */
index 4aa5b83bff60424dbeaa14ea47a7c185be6f3303..c9ea7a94f4e6002344674760c14c79805c5bbcbd 100644 (file)
@@ -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 (file)
index 0000000..5cb23aa
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2014 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+
+#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 (file)
index 0000000..96a7a0f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2014 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __OPERATION_H
+#define __OPERATION_H
+
+#include <linux/completion.h>
+
+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 */