Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Greybus connections |
| 3 | * |
| 4 | * Copyright 2014 Google Inc. |
Alex Elder | a46e967 | 2014-12-12 12:08:42 -0600 | [diff] [blame] | 5 | * Copyright 2014 Linaro Ltd. |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 6 | * |
| 7 | * Released under the GPLv2 only. |
| 8 | */ |
| 9 | |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 10 | #include <linux/atomic.h> |
| 11 | |
| 12 | #include "kernel_ver.h" |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 13 | #include "greybus.h" |
| 14 | |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 15 | static DEFINE_SPINLOCK(gb_connections_lock); |
| 16 | |
Viresh Kumar | 12eba9f | 2015-05-20 16:48:00 +0530 | [diff] [blame] | 17 | struct gb_connection *gb_connection_hd_find(struct greybus_host_device *hd, |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 18 | u16 cport_id) |
| 19 | { |
| 20 | struct gb_connection *connection = NULL; |
Johan Hovold | 8f5eadb | 2015-03-02 09:55:26 +0100 | [diff] [blame] | 21 | unsigned long flags; |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 22 | |
Johan Hovold | 8f5eadb | 2015-03-02 09:55:26 +0100 | [diff] [blame] | 23 | spin_lock_irqsave(&gb_connections_lock, flags); |
Alex Elder | 2c43ce4 | 2014-11-17 08:08:44 -0600 | [diff] [blame] | 24 | list_for_each_entry(connection, &hd->connections, hd_links) |
| 25 | if (connection->hd_cport_id == cport_id) |
Marti Bolivar | e86905b | 2014-10-06 13:26:02 -0400 | [diff] [blame] | 26 | goto found; |
Marti Bolivar | e86905b | 2014-10-06 13:26:02 -0400 | [diff] [blame] | 27 | connection = NULL; |
| 28 | found: |
Johan Hovold | 8f5eadb | 2015-03-02 09:55:26 +0100 | [diff] [blame] | 29 | spin_unlock_irqrestore(&gb_connections_lock, flags); |
Alex Elder | ee9ebe4 | 2014-10-06 06:53:08 -0500 | [diff] [blame] | 30 | |
| 31 | return connection; |
| 32 | } |
| 33 | |
Alex Elder | de3557d | 2014-11-20 16:09:18 -0600 | [diff] [blame] | 34 | /* |
| 35 | * Callback from the host driver to let us know that data has been |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 36 | * received on the bundle. |
Alex Elder | de3557d | 2014-11-20 16:09:18 -0600 | [diff] [blame] | 37 | */ |
| 38 | void greybus_data_rcvd(struct greybus_host_device *hd, u16 cport_id, |
Alex Elder | 374e6a2 | 2014-11-17 18:08:37 -0600 | [diff] [blame] | 39 | u8 *data, size_t length) |
| 40 | { |
| 41 | struct gb_connection *connection; |
| 42 | |
Viresh Kumar | 12eba9f | 2015-05-20 16:48:00 +0530 | [diff] [blame] | 43 | connection = gb_connection_hd_find(hd, cport_id); |
Alex Elder | 374e6a2 | 2014-11-17 18:08:37 -0600 | [diff] [blame] | 44 | if (!connection) { |
| 45 | dev_err(hd->parent, |
| 46 | "nonexistent connection (%zu bytes dropped)\n", length); |
| 47 | return; |
| 48 | } |
Alex Elder | 61089e8 | 2014-11-18 13:26:50 -0600 | [diff] [blame] | 49 | gb_connection_recv(connection, data, length); |
Alex Elder | 374e6a2 | 2014-11-17 18:08:37 -0600 | [diff] [blame] | 50 | } |
Alex Elder | de3557d | 2014-11-20 16:09:18 -0600 | [diff] [blame] | 51 | EXPORT_SYMBOL_GPL(greybus_data_rcvd); |
Alex Elder | 374e6a2 | 2014-11-17 18:08:37 -0600 | [diff] [blame] | 52 | |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 53 | /* |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 54 | * Allocate an available CPort Id for use for the host side of the |
| 55 | * given connection. The lowest-available id is returned, so the |
| 56 | * first call is guaranteed to allocate CPort Id 0. |
| 57 | * |
| 58 | * Assigns the connection's hd_cport_id and returns true if successful. |
| 59 | * Returns false otherwise. |
| 60 | */ |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 61 | static bool gb_connection_hd_cport_id_alloc(struct gb_connection *connection) |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 62 | { |
| 63 | struct ida *ida = &connection->hd->cport_id_map; |
| 64 | int id; |
| 65 | |
Johan Hovold | 067f3b6 | 2015-02-12 11:22:47 +0800 | [diff] [blame] | 66 | id = ida_simple_get(ida, 0, HOST_DEV_CPORT_ID_MAX, GFP_ATOMIC); |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 67 | if (id < 0) |
| 68 | return false; |
| 69 | |
| 70 | connection->hd_cport_id = (u16)id; |
| 71 | |
| 72 | return true; |
| 73 | } |
| 74 | |
| 75 | /* |
| 76 | * Free a previously-allocated CPort Id on the given host device. |
| 77 | */ |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 78 | static void gb_connection_hd_cport_id_free(struct gb_connection *connection) |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 79 | { |
| 80 | struct ida *ida = &connection->hd->cport_id_map; |
| 81 | |
| 82 | ida_simple_remove(ida, connection->hd_cport_id); |
| 83 | connection->hd_cport_id = CPORT_ID_BAD; |
| 84 | } |
| 85 | |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 86 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, |
| 87 | char *buf) |
| 88 | { |
| 89 | struct gb_connection *connection = to_gb_connection(dev); |
| 90 | |
Greg Kroah-Hartman | 88e70a6 | 2014-12-23 16:09:26 -0800 | [diff] [blame] | 91 | return sprintf(buf, "%d\n", connection->state); |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 92 | } |
| 93 | static DEVICE_ATTR_RO(state); |
| 94 | |
Alex Elder | 7fba007 | 2014-10-28 19:35:59 -0500 | [diff] [blame] | 95 | static ssize_t |
| 96 | protocol_id_show(struct device *dev, struct device_attribute *attr, char *buf) |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 97 | { |
| 98 | struct gb_connection *connection = to_gb_connection(dev); |
| 99 | |
Greg Kroah-Hartman | 88e70a6 | 2014-12-23 16:09:26 -0800 | [diff] [blame] | 100 | return sprintf(buf, "%d\n", connection->protocol->id); |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 101 | } |
Alex Elder | 7fba007 | 2014-10-28 19:35:59 -0500 | [diff] [blame] | 102 | static DEVICE_ATTR_RO(protocol_id); |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 103 | |
| 104 | static struct attribute *connection_attrs[] = { |
| 105 | &dev_attr_state.attr, |
Alex Elder | 7fba007 | 2014-10-28 19:35:59 -0500 | [diff] [blame] | 106 | &dev_attr_protocol_id.attr, |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 107 | NULL, |
| 108 | }; |
| 109 | |
| 110 | ATTRIBUTE_GROUPS(connection); |
| 111 | |
| 112 | static void gb_connection_release(struct device *dev) |
| 113 | { |
| 114 | struct gb_connection *connection = to_gb_connection(dev); |
| 115 | |
| 116 | kfree(connection); |
| 117 | } |
| 118 | |
Greg Kroah-Hartman | 0ac5a83 | 2014-11-15 12:12:16 -0800 | [diff] [blame] | 119 | struct device_type greybus_connection_type = { |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 120 | .name = "greybus_connection", |
| 121 | .release = gb_connection_release, |
| 122 | }; |
| 123 | |
Greg Kroah-Hartman | fb69cb5 | 2014-12-23 15:16:53 -0800 | [diff] [blame] | 124 | |
| 125 | void gb_connection_bind_protocol(struct gb_connection *connection) |
| 126 | { |
| 127 | struct gb_bundle *bundle; |
| 128 | struct gb_protocol *protocol; |
| 129 | |
| 130 | /* If we already have a protocol bound here, just return */ |
| 131 | if (connection->protocol) |
| 132 | return; |
| 133 | |
| 134 | protocol = gb_protocol_get(connection->protocol_id, |
| 135 | connection->major, |
| 136 | connection->minor); |
| 137 | if (!protocol) |
| 138 | return; |
| 139 | connection->protocol = protocol; |
| 140 | |
| 141 | /* |
| 142 | * If we have a valid device_id for the bundle, then we have an active |
| 143 | * device, so bring up the connection at the same time. |
| 144 | * */ |
| 145 | bundle = connection->bundle; |
Greg Kroah-Hartman | 1b6ea0d | 2014-12-24 13:01:39 -0800 | [diff] [blame] | 146 | if (bundle->device_id != GB_DEVICE_ID_BAD) |
Greg Kroah-Hartman | fb69cb5 | 2014-12-23 15:16:53 -0800 | [diff] [blame] | 147 | gb_connection_init(connection); |
| 148 | } |
| 149 | |
Alex Elder | 177404b | 2014-10-03 14:14:24 -0500 | [diff] [blame] | 150 | /* |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 151 | * Set up a Greybus connection, representing the bidirectional link |
| 152 | * between a CPort on a (local) Greybus host device and a CPort on |
| 153 | * another Greybus module. |
| 154 | * |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 155 | * A connection also maintains the state of operations sent over the |
| 156 | * connection. |
| 157 | * |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 158 | * Returns a pointer to the new connection if successful, or a null |
| 159 | * pointer otherwise. |
| 160 | */ |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 161 | struct gb_connection *gb_connection_create(struct gb_bundle *bundle, |
Alex Elder | 7fba007 | 2014-10-28 19:35:59 -0500 | [diff] [blame] | 162 | u16 cport_id, u8 protocol_id) |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 163 | { |
| 164 | struct gb_connection *connection; |
Alex Elder | cd34507 | 2014-10-02 12:30:05 -0500 | [diff] [blame] | 165 | struct greybus_host_device *hd; |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 166 | int retval; |
Alex Elder | 6ae7fa4 | 2014-11-05 16:12:50 -0600 | [diff] [blame] | 167 | u8 major = 0; |
| 168 | u8 minor = 1; |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 169 | |
| 170 | connection = kzalloc(sizeof(*connection), GFP_KERNEL); |
| 171 | if (!connection) |
| 172 | return NULL; |
| 173 | |
Greg Kroah-Hartman | fb69cb5 | 2014-12-23 15:16:53 -0800 | [diff] [blame] | 174 | connection->protocol_id = protocol_id; |
| 175 | connection->major = major; |
| 176 | connection->minor = minor; |
Alex Elder | 4ccb6b7 | 2014-10-28 19:36:00 -0500 | [diff] [blame] | 177 | |
Greg Kroah-Hartman | 4ab9b3c | 2014-12-19 14:56:31 -0800 | [diff] [blame] | 178 | hd = bundle->intf->hd; |
Greg Kroah-Hartman | 4b640bb | 2014-10-29 09:57:08 +0800 | [diff] [blame] | 179 | connection->hd = hd; |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 180 | if (!gb_connection_hd_cport_id_alloc(connection)) { |
Alex Elder | 0e44765 | 2014-11-05 16:12:51 -0600 | [diff] [blame] | 181 | gb_protocol_put(connection->protocol); |
Alex Elder | 9e8a686 | 2014-10-02 12:30:04 -0500 | [diff] [blame] | 182 | kfree(connection); |
| 183 | return NULL; |
| 184 | } |
Greg Kroah-Hartman | 25b7b6d | 2014-10-06 20:29:40 -0700 | [diff] [blame] | 185 | |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 186 | connection->bundle = bundle; |
| 187 | connection->bundle_cport_id = cport_id; |
Alex Elder | 36561f2 | 2014-10-22 02:04:30 -0500 | [diff] [blame] | 188 | connection->state = GB_CONNECTION_STATE_DISABLED; |
Alex Elder | ad1c449 | 2014-10-02 12:30:06 -0500 | [diff] [blame] | 189 | |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 190 | connection->dev.parent = &bundle->dev; |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 191 | connection->dev.bus = &greybus_bus_type; |
| 192 | connection->dev.type = &greybus_connection_type; |
| 193 | connection->dev.groups = connection_groups; |
| 194 | device_initialize(&connection->dev); |
| 195 | dev_set_name(&connection->dev, "%s:%d", |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 196 | dev_name(&bundle->dev), cport_id); |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 197 | |
| 198 | retval = device_add(&connection->dev); |
| 199 | if (retval) { |
Alex Elder | 6b09938 | 2014-11-05 16:03:12 -0600 | [diff] [blame] | 200 | pr_err("failed to add connection device for cport 0x%04hx\n", |
| 201 | cport_id); |
Alex Elder | b296996 | 2014-10-28 19:35:58 -0500 | [diff] [blame] | 202 | gb_connection_hd_cport_id_free(connection); |
Alex Elder | 0e44765 | 2014-11-05 16:12:51 -0600 | [diff] [blame] | 203 | gb_protocol_put(connection->protocol); |
Greg Kroah-Hartman | 4b640bb | 2014-10-29 09:57:08 +0800 | [diff] [blame] | 204 | put_device(&connection->dev); |
Viresh Kumar | a68bd74 | 2014-11-13 18:14:39 +0530 | [diff] [blame] | 205 | kfree(connection); |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 206 | return NULL; |
| 207 | } |
| 208 | |
Greg Kroah-Hartman | fb69cb5 | 2014-12-23 15:16:53 -0800 | [diff] [blame] | 209 | /* XXX Will have to establish connections to get version */ |
| 210 | gb_connection_bind_protocol(connection); |
| 211 | if (!connection->protocol) |
| 212 | dev_warn(&bundle->dev, |
| 213 | "protocol 0x%02hhx handler not found\n", protocol_id); |
| 214 | |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 215 | spin_lock_irq(&gb_connections_lock); |
Alex Elder | 2c43ce4 | 2014-11-17 08:08:44 -0600 | [diff] [blame] | 216 | list_add_tail(&connection->hd_links, &hd->connections); |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 217 | list_add_tail(&connection->bundle_links, &bundle->connections); |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 218 | spin_unlock_irq(&gb_connections_lock); |
| 219 | |
Alex Elder | 4afb7fd | 2014-12-03 08:35:08 -0600 | [diff] [blame] | 220 | atomic_set(&connection->op_cycle, 0); |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 221 | INIT_LIST_HEAD(&connection->operations); |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 222 | |
| 223 | return connection; |
| 224 | } |
| 225 | |
| 226 | /* |
| 227 | * Tear down a previously set up connection. |
| 228 | */ |
| 229 | void gb_connection_destroy(struct gb_connection *connection) |
| 230 | { |
Alex Elder | e1158df | 2014-10-22 02:04:29 -0500 | [diff] [blame] | 231 | struct gb_operation *operation; |
| 232 | struct gb_operation *next; |
| 233 | |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 234 | if (WARN_ON(!connection)) |
| 235 | return; |
| 236 | |
| 237 | /* XXX Need to wait for any outstanding requests to complete */ |
Viresh Kumar | 38d61dd | 2014-11-14 17:25:02 +0530 | [diff] [blame] | 238 | if (WARN_ON(!list_empty(&connection->operations))) { |
| 239 | list_for_each_entry_safe(operation, next, |
| 240 | &connection->operations, links) |
Alex Elder | f68c05c | 2014-11-21 19:29:17 -0600 | [diff] [blame] | 241 | gb_operation_cancel(operation, -ESHUTDOWN); |
Alex Elder | e1158df | 2014-10-22 02:04:29 -0500 | [diff] [blame] | 242 | } |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 243 | spin_lock_irq(&gb_connections_lock); |
Greg Kroah-Hartman | 1db0a5f | 2014-12-12 17:10:17 -0500 | [diff] [blame] | 244 | list_del(&connection->bundle_links); |
Alex Elder | 2c43ce4 | 2014-11-17 08:08:44 -0600 | [diff] [blame] | 245 | list_del(&connection->hd_links); |
Alex Elder | 748e123 | 2014-10-03 14:14:22 -0500 | [diff] [blame] | 246 | spin_unlock_irq(&gb_connections_lock); |
| 247 | |
Alex Elder | f6aec25 | 2014-10-06 06:53:06 -0500 | [diff] [blame] | 248 | gb_connection_hd_cport_id_free(connection); |
Alex Elder | 0e44765 | 2014-11-05 16:12:51 -0600 | [diff] [blame] | 249 | gb_protocol_put(connection->protocol); |
Greg Kroah-Hartman | f0f61b9 | 2014-10-24 17:34:46 +0800 | [diff] [blame] | 250 | |
Viresh Kumar | 2352a73 | 2015-04-02 17:53:47 +0530 | [diff] [blame] | 251 | device_unregister(&connection->dev); |
Alex Elder | c68adb2 | 2014-10-01 21:54:14 -0500 | [diff] [blame] | 252 | } |
Alex Elder | e88afa5 | 2014-10-01 21:54:15 -0500 | [diff] [blame] | 253 | |
Alex Elder | 574341c | 2014-10-16 06:35:35 -0500 | [diff] [blame] | 254 | int gb_connection_init(struct gb_connection *connection) |
| 255 | { |
Alex Elder | 36561f2 | 2014-10-22 02:04:30 -0500 | [diff] [blame] | 256 | int ret; |
| 257 | |
Alex Elder | 5d9fd7e | 2014-11-05 16:12:54 -0600 | [diff] [blame] | 258 | if (!connection->protocol) { |
Greg Kroah-Hartman | fb69cb5 | 2014-12-23 15:16:53 -0800 | [diff] [blame] | 259 | dev_warn(&connection->dev, "init without protocol.\n"); |
| 260 | return 0; |
Alex Elder | 574341c | 2014-10-16 06:35:35 -0500 | [diff] [blame] | 261 | } |
Alex Elder | 36561f2 | 2014-10-22 02:04:30 -0500 | [diff] [blame] | 262 | |
Alex Elder | 5d9fd7e | 2014-11-05 16:12:54 -0600 | [diff] [blame] | 263 | /* Need to enable the connection to initialize it */ |
| 264 | connection->state = GB_CONNECTION_STATE_ENABLED; |
| 265 | ret = connection->protocol->connection_init(connection); |
Alex Elder | 36561f2 | 2014-10-22 02:04:30 -0500 | [diff] [blame] | 266 | if (ret) |
| 267 | connection->state = GB_CONNECTION_STATE_ERROR; |
| 268 | |
| 269 | return ret; |
Alex Elder | 574341c | 2014-10-16 06:35:35 -0500 | [diff] [blame] | 270 | } |
Alex Elder | 697e55d | 2014-10-20 23:01:04 -0500 | [diff] [blame] | 271 | |
| 272 | void gb_connection_exit(struct gb_connection *connection) |
| 273 | { |
Alex Elder | 5d9fd7e | 2014-11-05 16:12:54 -0600 | [diff] [blame] | 274 | if (!connection->protocol) { |
Greg Kroah-Hartman | fb69cb5 | 2014-12-23 15:16:53 -0800 | [diff] [blame] | 275 | dev_warn(&connection->dev, "exit without protocol.\n"); |
Alex Elder | 3689f97 | 2014-10-27 06:04:30 -0500 | [diff] [blame] | 276 | return; |
Alex Elder | 697e55d | 2014-10-20 23:01:04 -0500 | [diff] [blame] | 277 | } |
Johan Hovold | 4453839 | 2015-03-17 10:55:52 +0100 | [diff] [blame] | 278 | |
| 279 | if (connection->state != GB_CONNECTION_STATE_ENABLED) |
| 280 | return; |
| 281 | |
Alex Elder | 3689f97 | 2014-10-27 06:04:30 -0500 | [diff] [blame] | 282 | connection->state = GB_CONNECTION_STATE_DESTROYING; |
Alex Elder | 5d9fd7e | 2014-11-05 16:12:54 -0600 | [diff] [blame] | 283 | connection->protocol->connection_exit(connection); |
Alex Elder | 697e55d | 2014-10-20 23:01:04 -0500 | [diff] [blame] | 284 | } |