Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Greybus connections |
| 3 | * |
| 4 | * Copyright 2014 Google Inc. |
| 5 | * |
| 6 | * Released under the GPLv2 only. |
| 7 | */ |
| 8 | |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 9 | #include <linux/atomic.h> |
| 10 | |
| 11 | #include "kernel_ver.h" |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 12 | #include "greybus.h" |
| 13 | |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 14 | static DEFINE_SPINLOCK(gb_connections_lock); |
| 15 | |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 16 | static void _gb_hd_connection_insert(struct greybus_host_device *hd, |
| 17 | struct gb_connection *connection) |
| 18 | { |
| 19 | struct rb_root *root = &hd->connections; |
| 20 | struct rb_node *node = &connection->hd_node; |
| 21 | struct rb_node **link = &root->rb_node; |
| 22 | struct rb_node *above = NULL; |
| 23 | u16 cport_id = connection->hd_cport_id; |
| 24 | |
| 25 | while (*link) { |
| 26 | struct gb_connection *connection; |
| 27 | |
| 28 | above = *link; |
| 29 | connection = rb_entry(above, struct gb_connection, hd_node); |
| 30 | if (connection->hd_cport_id > cport_id) |
| 31 | link = &above->rb_left; |
| 32 | else if (connection->hd_cport_id < cport_id) |
| 33 | link = &above->rb_right; |
| 34 | } |
| 35 | rb_link_node(node, above, link); |
| 36 | rb_insert_color(node, root); |
| 37 | } |
| 38 | |
| 39 | static void _gb_hd_connection_remove(struct gb_connection *connection) |
| 40 | { |
| 41 | rb_erase(&connection->hd_node, &connection->hd->connections); |
| 42 | } |
| 43 | |
| 44 | struct gb_connection *gb_hd_connection_find(struct greybus_host_device *hd, |
| 45 | u16 cport_id) |
| 46 | { |
| 47 | struct gb_connection *connection = NULL; |
| 48 | struct rb_node *node; |
| 49 | |
| 50 | spin_lock_irq(&gb_connections_lock); |
| 51 | node = hd->connections.rb_node; |
| 52 | while (node) { |
| 53 | connection = rb_entry(node, struct gb_connection, hd_node); |
| 54 | if (connection->hd_cport_id > cport_id) |
| 55 | node = node->rb_left; |
| 56 | else if (connection->hd_cport_id < cport_id) |
| 57 | node = node->rb_right; |
| 58 | else |
Marti Bolivar | e86905b | 2014-10-06 13:26:02 -0400 | [diff] [blame] | 59 | goto found; |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 60 | } |
Marti Bolivar | e86905b | 2014-10-06 13:26:02 -0400 | [diff] [blame] | 61 | connection = NULL; |
| 62 | found: |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 63 | spin_unlock_irq(&gb_connections_lock); |
| 64 | |
| 65 | return connection; |
| 66 | } |
| 67 | |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 68 | /* |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 69 | * Allocate an available CPort Id for use for the host side of the |
| 70 | * given connection. The lowest-available id is returned, so the |
| 71 | * first call is guaranteed to allocate CPort Id 0. |
| 72 | * |
| 73 | * Assigns the connection's hd_cport_id and returns true if successful. |
| 74 | * Returns false otherwise. |
| 75 | */ |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 76 | static bool gb_connection_hd_cport_id_alloc(struct gb_connection *connection) |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 77 | { |
| 78 | struct ida *ida = &connection->hd->cport_id_map; |
| 79 | int id; |
| 80 | |
Greg Kroah-Hartman | 25b7b6d | 2014-10-06 20:29:40 -0700 | [diff] [blame] | 81 | spin_lock(&connection->hd->cport_id_map_lock); |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 82 | id = ida_simple_get(ida, 0, HOST_DEV_CPORT_ID_MAX, GFP_KERNEL); |
Greg Kroah-Hartman | 25b7b6d | 2014-10-06 20:29:40 -0700 | [diff] [blame] | 83 | spin_unlock(&connection->hd->cport_id_map_lock); |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 84 | if (id < 0) |
| 85 | return false; |
| 86 | |
| 87 | connection->hd_cport_id = (u16)id; |
| 88 | |
| 89 | return true; |
| 90 | } |
| 91 | |
| 92 | /* |
| 93 | * Free a previously-allocated CPort Id on the given host device. |
| 94 | */ |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 95 | static void gb_connection_hd_cport_id_free(struct gb_connection *connection) |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 96 | { |
| 97 | struct ida *ida = &connection->hd->cport_id_map; |
| 98 | |
Greg Kroah-Hartman | 25b7b6d | 2014-10-06 20:29:40 -0700 | [diff] [blame] | 99 | spin_lock(&connection->hd->cport_id_map_lock); |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 100 | ida_simple_remove(ida, connection->hd_cport_id); |
Greg Kroah-Hartman | 25b7b6d | 2014-10-06 20:29:40 -0700 | [diff] [blame] | 101 | spin_unlock(&connection->hd->cport_id_map_lock); |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 102 | connection->hd_cport_id = CPORT_ID_BAD; |
| 103 | } |
| 104 | |
| 105 | /* |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 106 | * Set up a Greybus connection, representing the bidirectional link |
| 107 | * between a CPort on a (local) Greybus host device and a CPort on |
| 108 | * another Greybus module. |
| 109 | * |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 110 | * A connection also maintains the state of operations sent over the |
| 111 | * connection. |
| 112 | * |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 113 | * Returns a pointer to the new connection if successful, or a null |
| 114 | * pointer otherwise. |
| 115 | */ |
Alex Elder | cd34507 | 2014-10-02 12:30:05 -0500 | [diff] [blame] | 116 | struct gb_connection *gb_connection_create(struct gb_interface *interface, |
Alex Elder | ad1c449 | 2014-10-02 12:30:06 -0500 | [diff] [blame] | 117 | u16 cport_id, enum greybus_protocol protocol) |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 118 | { |
| 119 | struct gb_connection *connection; |
Alex Elder | cd34507 | 2014-10-02 12:30:05 -0500 | [diff] [blame] | 120 | struct greybus_host_device *hd; |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 121 | |
| 122 | connection = kzalloc(sizeof(*connection), GFP_KERNEL); |
| 123 | if (!connection) |
| 124 | return NULL; |
| 125 | |
Alex Elder | cd34507 | 2014-10-02 12:30:05 -0500 | [diff] [blame] | 126 | hd = interface->gmod->hd; |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 127 | connection->hd = hd; /* XXX refcount? */ |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 128 | if (!gb_connection_hd_cport_id_alloc(connection)) { |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 129 | /* kref_put(connection->hd); */ |
Alex Elder | 9e8a686 | 2014-10-02 12:30:04 -0500 | [diff] [blame] | 130 | kfree(connection); |
| 131 | return NULL; |
| 132 | } |
Greg Kroah-Hartman | 25b7b6d | 2014-10-06 20:29:40 -0700 | [diff] [blame] | 133 | |
Alex Elder | cd34507 | 2014-10-02 12:30:05 -0500 | [diff] [blame] | 134 | connection->interface = interface; /* XXX refcount? */ |
| 135 | connection->interface_cport_id = cport_id; |
Alex Elder | ad1c449 | 2014-10-02 12:30:06 -0500 | [diff] [blame] | 136 | connection->protocol = protocol; |
| 137 | |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 138 | spin_lock_irq(&gb_connections_lock); |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 139 | _gb_hd_connection_insert(hd, connection); |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 140 | list_add_tail(&connection->interface_links, &interface->connections); |
| 141 | spin_unlock_irq(&gb_connections_lock); |
| 142 | |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 143 | INIT_LIST_HEAD(&connection->operations); |
Alex Elder | 84d148b | 2014-10-16 06:35:32 -0500 | [diff] [blame] | 144 | connection->pending = RB_ROOT; |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 145 | atomic_set(&connection->op_cycle, 0); |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 146 | |
| 147 | return connection; |
| 148 | } |
| 149 | |
| 150 | /* |
| 151 | * Tear down a previously set up connection. |
| 152 | */ |
| 153 | void gb_connection_destroy(struct gb_connection *connection) |
| 154 | { |
| 155 | if (WARN_ON(!connection)) |
| 156 | return; |
| 157 | |
| 158 | /* XXX Need to wait for any outstanding requests to complete */ |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 159 | WARN_ON(!list_empty(&connection->operations)); |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 160 | |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 161 | spin_lock_irq(&gb_connections_lock); |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 162 | list_del(&connection->interface_links); |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 163 | _gb_hd_connection_remove(connection); |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 164 | spin_unlock_irq(&gb_connections_lock); |
| 165 | |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 166 | gb_connection_hd_cport_id_free(connection); |
Alex Elder | cd34507 | 2014-10-02 12:30:05 -0500 | [diff] [blame] | 167 | /* kref_put(connection->interface); */ |
| 168 | /* kref_put(connection->hd); */ |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 169 | kfree(connection); |
| 170 | } |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 171 | |
Alex Elder | 84d148b | 2014-10-16 06:35:32 -0500 | [diff] [blame] | 172 | u16 gb_connection_operation_id(struct gb_connection *connection) |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 173 | { |
| 174 | return (u16)(atomic_inc_return(&connection->op_cycle) % U16_MAX); |
| 175 | } |
Alex Elder | eeeed42 | 2014-10-03 15:05:22 -0500 | [diff] [blame] | 176 | |
| 177 | void gb_connection_err(struct gb_connection *connection, const char *fmt, ...) |
| 178 | { |
| 179 | struct va_format vaf; |
| 180 | va_list args; |
| 181 | |
| 182 | va_start(args, fmt); |
| 183 | |
| 184 | vaf.fmt = fmt; |
| 185 | vaf.va = &args; |
| 186 | |
| 187 | pr_err("greybus: [%hhu:%hhu:%hu]: %pV\n", |
| 188 | connection->interface->gmod->module_id, |
| 189 | connection->interface->id, |
| 190 | connection->interface_cport_id, &vaf); |
| 191 | |
| 192 | va_end(args); |
| 193 | } |
Alex Elder | 574341c | 2014-10-16 06:35:35 -0500 | [diff] [blame] | 194 | |
| 195 | /* |
| 196 | * XXX Protocols should have a set of function pointers: |
| 197 | * ->init (called here, to initialize the device) |
| 198 | * ->input_handler |
| 199 | * ->exit (reverse of init) |
| 200 | */ |
| 201 | int gb_connection_init(struct gb_connection *connection) |
| 202 | { |
| 203 | switch (connection->protocol) { |
| 204 | case GREYBUS_PROTOCOL_I2C: |
Alex Elder | ed8800d | 2014-10-16 06:35:38 -0500 | [diff] [blame^] | 205 | return gb_i2c_device_init(connection); |
Alex Elder | 574341c | 2014-10-16 06:35:35 -0500 | [diff] [blame] | 206 | case GREYBUS_PROTOCOL_CONTROL: |
| 207 | case GREYBUS_PROTOCOL_AP: |
| 208 | case GREYBUS_PROTOCOL_GPIO: |
| 209 | case GREYBUS_PROTOCOL_UART: |
| 210 | case GREYBUS_PROTOCOL_HID: |
| 211 | case GREYBUS_PROTOCOL_VENDOR: |
| 212 | default: |
| 213 | gb_connection_err(connection, "unimplemented protocol %u", |
| 214 | (u32)connection->protocol); |
| 215 | break; |
| 216 | } |
| 217 | return -ENXIO; |
| 218 | } |