greybus: fix forced disable of offloaded connections
[cascardo/linux.git] / drivers / staging / greybus / core.c
1 /*
2  * Greybus "Core"
3  *
4  * Copyright 2014-2015 Google Inc.
5  * Copyright 2014-2015 Linaro Ltd.
6  *
7  * Released under the GPLv2 only.
8  */
9
10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11
12 #define CREATE_TRACE_POINTS
13 #include "bootrom.h"
14 #include "greybus.h"
15 #include "greybus_trace.h"
16
17 EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_create);
18 EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_release);
19 EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_add);
20 EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_del);
21
22 /* Allow greybus to be disabled at boot if needed */
23 static bool nogreybus;
24 #ifdef MODULE
25 module_param(nogreybus, bool, 0444);
26 #else
27 core_param(nogreybus, nogreybus, bool, 0444);
28 #endif
29 int greybus_disabled(void)
30 {
31         return nogreybus;
32 }
33 EXPORT_SYMBOL_GPL(greybus_disabled);
34
35 static int greybus_match_one_id(struct gb_bundle *bundle,
36                                      const struct greybus_bundle_id *id)
37 {
38         if ((id->match_flags & GREYBUS_ID_MATCH_VENDOR) &&
39             (id->vendor != bundle->intf->vendor_id))
40                 return 0;
41
42         if ((id->match_flags & GREYBUS_ID_MATCH_PRODUCT) &&
43             (id->product != bundle->intf->product_id))
44                 return 0;
45
46         if ((id->match_flags & GREYBUS_ID_MATCH_CLASS) &&
47             (id->class != bundle->class))
48                 return 0;
49
50         return 1;
51 }
52
53 static const struct greybus_bundle_id *
54 greybus_match_id(struct gb_bundle *bundle, const struct greybus_bundle_id *id)
55 {
56         if (id == NULL)
57                 return NULL;
58
59         for (; id->vendor || id->product || id->class || id->driver_info;
60                                                                         id++) {
61                 if (greybus_match_one_id(bundle, id))
62                         return id;
63         }
64
65         return NULL;
66 }
67
68 static int greybus_match_device(struct device *dev, struct device_driver *drv)
69 {
70         struct greybus_driver *driver = to_greybus_driver(drv);
71         struct gb_bundle *bundle;
72         const struct greybus_bundle_id *id;
73
74         if (!is_gb_bundle(dev))
75                 return 0;
76
77         bundle = to_gb_bundle(dev);
78
79         id = greybus_match_id(bundle, driver->id_table);
80         if (id)
81                 return 1;
82         /* FIXME - Dynamic ids? */
83         return 0;
84 }
85
86 static int greybus_uevent(struct device *dev, struct kobj_uevent_env *env)
87 {
88         struct gb_host_device *hd;
89         struct gb_module *module = NULL;
90         struct gb_interface *intf = NULL;
91         struct gb_control *control = NULL;
92         struct gb_bundle *bundle = NULL;
93         struct gb_svc *svc = NULL;
94
95         if (is_gb_host_device(dev)) {
96                 hd = to_gb_host_device(dev);
97         } else if (is_gb_module(dev)) {
98                 module = to_gb_module(dev);
99                 hd = module->hd;
100         } else if (is_gb_interface(dev)) {
101                 intf = to_gb_interface(dev);
102                 module = intf->module;
103                 hd = intf->hd;
104         } else if (is_gb_control(dev)) {
105                 control = to_gb_control(dev);
106                 intf = control->intf;
107                 module = intf->module;
108                 hd = intf->hd;
109         } else if (is_gb_bundle(dev)) {
110                 bundle = to_gb_bundle(dev);
111                 intf = bundle->intf;
112                 module = intf->module;
113                 hd = intf->hd;
114         } else if (is_gb_svc(dev)) {
115                 svc = to_gb_svc(dev);
116                 hd = svc->hd;
117         } else {
118                 dev_WARN(dev, "uevent for unknown greybus device \"type\"!\n");
119                 return -EINVAL;
120         }
121
122         if (add_uevent_var(env, "BUS=%u", hd->bus_id))
123                 return -ENOMEM;
124
125         if (module) {
126                 if (add_uevent_var(env, "MODULE=%u", module->module_id))
127                         return -ENOMEM;
128         }
129
130         if (intf) {
131                 if (add_uevent_var(env, "INTERFACE=%u", intf->interface_id))
132                         return -ENOMEM;
133                 if (add_uevent_var(env, "GREYBUS_ID=%08x/%08x",
134                                    intf->vendor_id, intf->product_id))
135                         return -ENOMEM;
136         }
137
138         if (bundle) {
139                 // FIXME
140                 // add a uevent that can "load" a bundle type
141                 // This is what we need to bind a driver to so use the info
142                 // in gmod here as well
143
144                 if (add_uevent_var(env, "BUNDLE=%u", bundle->id))
145                         return -ENOMEM;
146                 if (add_uevent_var(env, "BUNDLE_CLASS=%02x", bundle->class))
147                         return -ENOMEM;
148         }
149
150         return 0;
151 }
152
153 struct bus_type greybus_bus_type = {
154         .name =         "greybus",
155         .match =        greybus_match_device,
156         .uevent =       greybus_uevent,
157 };
158
159 static int greybus_probe(struct device *dev)
160 {
161         struct greybus_driver *driver = to_greybus_driver(dev->driver);
162         struct gb_bundle *bundle = to_gb_bundle(dev);
163         const struct greybus_bundle_id *id;
164         int retval;
165
166         /* match id */
167         id = greybus_match_id(bundle, driver->id_table);
168         if (!id)
169                 return -ENODEV;
170
171         retval = driver->probe(bundle, id);
172         if (retval) {
173                 /*
174                  * Catch buggy drivers that fail to destroy their connections.
175                  */
176                 WARN_ON(!list_empty(&bundle->connections));
177
178                 return retval;
179         }
180
181         gb_timesync_schedule_asynchronous(bundle->intf);
182
183         return 0;
184 }
185
186 static int greybus_remove(struct device *dev)
187 {
188         struct greybus_driver *driver = to_greybus_driver(dev->driver);
189         struct gb_bundle *bundle = to_gb_bundle(dev);
190         struct gb_connection *connection;
191
192         /*
193          * Disable (non-offloaded) connections early in case the interface is
194          * already gone to avoid unceccessary operation timeouts during
195          * driver disconnect. Otherwise, only disable incoming requests.
196          */
197         list_for_each_entry(connection, &bundle->connections, bundle_links) {
198                 if (gb_connection_is_offloaded(connection))
199                         continue;
200
201                 if (bundle->intf->disconnected)
202                         gb_connection_disable_forced(connection);
203                 else
204                         gb_connection_disable_rx(connection);
205         }
206
207         driver->disconnect(bundle);
208
209         /* Catch buggy drivers that fail to destroy their connections. */
210         WARN_ON(!list_empty(&bundle->connections));
211
212         return 0;
213 }
214
215 int greybus_register_driver(struct greybus_driver *driver, struct module *owner,
216                 const char *mod_name)
217 {
218         int retval;
219
220         if (greybus_disabled())
221                 return -ENODEV;
222
223         driver->driver.bus = &greybus_bus_type;
224         driver->driver.name = driver->name;
225         driver->driver.probe = greybus_probe;
226         driver->driver.remove = greybus_remove;
227         driver->driver.owner = owner;
228         driver->driver.mod_name = mod_name;
229
230         retval = driver_register(&driver->driver);
231         if (retval)
232                 return retval;
233
234         pr_info("registered new driver %s\n", driver->name);
235         return 0;
236 }
237 EXPORT_SYMBOL_GPL(greybus_register_driver);
238
239 void greybus_deregister_driver(struct greybus_driver *driver)
240 {
241         driver_unregister(&driver->driver);
242 }
243 EXPORT_SYMBOL_GPL(greybus_deregister_driver);
244
245 static int __init gb_init(void)
246 {
247         int retval;
248
249         if (greybus_disabled())
250                 return -ENODEV;
251
252         BUILD_BUG_ON(CPORT_ID_MAX >= (long)CPORT_ID_BAD);
253
254         gb_debugfs_init();
255
256         retval = bus_register(&greybus_bus_type);
257         if (retval) {
258                 pr_err("bus_register failed (%d)\n", retval);
259                 goto error_bus;
260         }
261
262         retval = gb_hd_init();
263         if (retval) {
264                 pr_err("gb_hd_init failed (%d)\n", retval);
265                 goto error_hd;
266         }
267
268         retval = gb_operation_init();
269         if (retval) {
270                 pr_err("gb_operation_init failed (%d)\n", retval);
271                 goto error_operation;
272         }
273
274         retval = gb_bootrom_init();
275         if (retval) {
276                 pr_err("gb_bootrom_init failed\n");
277                 goto error_bootrom;
278         }
279
280         retval = gb_timesync_init();
281         if (retval) {
282                 pr_err("gb_timesync_init failed\n");
283                 goto error_timesync;
284         }
285         return 0;       /* Success */
286
287 error_timesync:
288         gb_bootrom_exit();
289 error_bootrom:
290         gb_operation_exit();
291 error_operation:
292         gb_hd_exit();
293 error_hd:
294         bus_unregister(&greybus_bus_type);
295 error_bus:
296         gb_debugfs_cleanup();
297
298         return retval;
299 }
300 module_init(gb_init);
301
302 static void __exit gb_exit(void)
303 {
304         gb_timesync_exit();
305         gb_bootrom_exit();
306         gb_operation_exit();
307         gb_hd_exit();
308         bus_unregister(&greybus_bus_type);
309         gb_debugfs_cleanup();
310         tracepoint_synchronize_unregister();
311 }
312 module_exit(gb_exit);
313 MODULE_LICENSE("GPL v2");
314 MODULE_AUTHOR("Greg Kroah-Hartman <gregkh@linuxfoundation.org>");