Commit | Line | Data |
---|---|---|
eb50fd3a | 1 | // SPDX-License-Identifier: GPL-2.0 |
7bc6faac JH |
2 | /* |
3 | * Greybus Host Device | |
4 | * | |
5 | * Copyright 2014-2015 Google Inc. | |
6 | * Copyright 2014-2015 Linaro Ltd. | |
7bc6faac JH |
7 | */ |
8 | ||
7bc6faac JH |
9 | #include <linux/kernel.h> |
10 | #include <linux/slab.h> | |
ec0ad868 | 11 | #include <linux/greybus.h> |
7bc6faac | 12 | |
1f79046b | 13 | #include "greybus_trace.h" |
7bc6faac | 14 | |
56278c73 VK |
15 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_create); |
16 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_release); | |
17 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_add); | |
18 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_del); | |
495787a7 | 19 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_in); |
56278c73 | 20 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_message_submit); |
495787a7 | 21 | |
2adaefb1 | 22 | static struct ida gb_hd_bus_id_map; |
7bc6faac | 23 | |
ed4596e9 GKH |
24 | int gb_hd_output(struct gb_host_device *hd, void *req, u16 size, u8 cmd, |
25 | bool async) | |
26 | { | |
27 | if (!hd || !hd->driver || !hd->driver->output) | |
28 | return -EINVAL; | |
29 | return hd->driver->output(hd, req, size, cmd, async); | |
30 | } | |
31 | EXPORT_SYMBOL_GPL(gb_hd_output); | |
32 | ||
fbbd2b7c | 33 | static ssize_t bus_id_show(struct device *dev, |
a11ac9ef | 34 | struct device_attribute *attr, char *buf) |
fbbd2b7c SP |
35 | { |
36 | struct gb_host_device *hd = to_gb_host_device(dev); | |
37 | ||
38 | return sprintf(buf, "%d\n", hd->bus_id); | |
39 | } | |
40 | static DEVICE_ATTR_RO(bus_id); | |
41 | ||
42 | static struct attribute *bus_attrs[] = { | |
43 | &dev_attr_bus_id.attr, | |
44 | NULL | |
45 | }; | |
46 | ATTRIBUTE_GROUPS(bus); | |
47 | ||
05061507 JH |
48 | int gb_hd_cport_reserve(struct gb_host_device *hd, u16 cport_id) |
49 | { | |
50 | struct ida *id_map = &hd->cport_id_map; | |
51 | int ret; | |
52 | ||
53 | ret = ida_simple_get(id_map, cport_id, cport_id + 1, GFP_KERNEL); | |
54 | if (ret < 0) { | |
55 | dev_err(&hd->dev, "failed to reserve cport %u\n", cport_id); | |
56 | return ret; | |
57 | } | |
58 | ||
59 | return 0; | |
60 | } | |
61 | EXPORT_SYMBOL_GPL(gb_hd_cport_reserve); | |
62 | ||
29a822bd VA |
63 | void gb_hd_cport_release_reserved(struct gb_host_device *hd, u16 cport_id) |
64 | { | |
65 | struct ida *id_map = &hd->cport_id_map; | |
66 | ||
67 | ida_simple_remove(id_map, cport_id); | |
68 | } | |
69 | EXPORT_SYMBOL_GPL(gb_hd_cport_release_reserved); | |
70 | ||
74a5d93c | 71 | /* Locking: Caller guarantees serialisation */ |
f2aae1c6 | 72 | int gb_hd_cport_allocate(struct gb_host_device *hd, int cport_id, |
a11ac9ef | 73 | unsigned long flags) |
74a5d93c JH |
74 | { |
75 | struct ida *id_map = &hd->cport_id_map; | |
76 | int ida_start, ida_end; | |
77 | ||
f2aae1c6 JH |
78 | if (hd->driver->cport_allocate) |
79 | return hd->driver->cport_allocate(hd, cport_id, flags); | |
80 | ||
74a5d93c JH |
81 | if (cport_id < 0) { |
82 | ida_start = 0; | |
83 | ida_end = hd->num_cports; | |
84 | } else if (cport_id < hd->num_cports) { | |
85 | ida_start = cport_id; | |
86 | ida_end = cport_id + 1; | |
87 | } else { | |
88 | dev_err(&hd->dev, "cport %d not available\n", cport_id); | |
89 | return -EINVAL; | |
90 | } | |
91 | ||
92 | return ida_simple_get(id_map, ida_start, ida_end, GFP_KERNEL); | |
93 | } | |
94 | ||
95 | /* Locking: Caller guarantees serialisation */ | |
96 | void gb_hd_cport_release(struct gb_host_device *hd, u16 cport_id) | |
97 | { | |
f2aae1c6 JH |
98 | if (hd->driver->cport_release) { |
99 | hd->driver->cport_release(hd, cport_id); | |
100 | return; | |
101 | } | |
102 | ||
74a5d93c JH |
103 | ida_simple_remove(&hd->cport_id_map, cport_id); |
104 | } | |
105 | ||
2adaefb1 | 106 | static void gb_hd_release(struct device *dev) |
7bc6faac | 107 | { |
2adaefb1 | 108 | struct gb_host_device *hd = to_gb_host_device(dev); |
7bc6faac | 109 | |
12823178 JH |
110 | trace_gb_hd_release(hd); |
111 | ||
7adeaae7 JH |
112 | if (hd->svc) |
113 | gb_svc_put(hd->svc); | |
2adaefb1 | 114 | ida_simple_remove(&gb_hd_bus_id_map, hd->bus_id); |
7bc6faac JH |
115 | ida_destroy(&hd->cport_id_map); |
116 | kfree(hd); | |
7bc6faac JH |
117 | } |
118 | ||
2adaefb1 JH |
119 | struct device_type greybus_hd_type = { |
120 | .name = "greybus_host_device", | |
121 | .release = gb_hd_release, | |
122 | }; | |
123 | ||
d6e139bc | 124 | struct gb_host_device *gb_hd_create(struct gb_hd_driver *driver, |
a11ac9ef GKH |
125 | struct device *parent, |
126 | size_t buffer_size_max, | |
127 | size_t num_cports) | |
7bc6faac | 128 | { |
2537636a | 129 | struct gb_host_device *hd; |
2adaefb1 | 130 | int ret; |
7bc6faac JH |
131 | |
132 | /* | |
133 | * Validate that the driver implements all of the callbacks | |
134 | * so that we don't have to every time we make them. | |
135 | */ | |
136 | if ((!driver->message_send) || (!driver->message_cancel)) { | |
b427572e | 137 | dev_err(parent, "mandatory hd-callbacks missing\n"); |
7bc6faac JH |
138 | return ERR_PTR(-EINVAL); |
139 | } | |
140 | ||
141 | if (buffer_size_max < GB_OPERATION_MESSAGE_SIZE_MIN) { | |
142 | dev_err(parent, "greybus host-device buffers too small\n"); | |
143 | return ERR_PTR(-EINVAL); | |
144 | } | |
145 | ||
bfe2c99c | 146 | if (num_cports == 0 || num_cports > CPORT_ID_MAX + 1) { |
7bc6faac JH |
147 | dev_err(parent, "Invalid number of CPorts: %zu\n", num_cports); |
148 | return ERR_PTR(-EINVAL); | |
149 | } | |
150 | ||
151 | /* | |
152 | * Make sure to never allocate messages larger than what the Greybus | |
153 | * protocol supports. | |
154 | */ | |
155 | if (buffer_size_max > GB_OPERATION_MESSAGE_SIZE_MAX) { | |
156 | dev_warn(parent, "limiting buffer size to %u\n", | |
157 | GB_OPERATION_MESSAGE_SIZE_MAX); | |
158 | buffer_size_max = GB_OPERATION_MESSAGE_SIZE_MAX; | |
159 | } | |
160 | ||
161 | hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL); | |
162 | if (!hd) | |
163 | return ERR_PTR(-ENOMEM); | |
164 | ||
2adaefb1 JH |
165 | ret = ida_simple_get(&gb_hd_bus_id_map, 1, 0, GFP_KERNEL); |
166 | if (ret < 0) { | |
167 | kfree(hd); | |
168 | return ERR_PTR(ret); | |
169 | } | |
2adaefb1 | 170 | hd->bus_id = ret; |
2adaefb1 | 171 | |
7bc6faac | 172 | hd->driver = driver; |
b15d97d7 | 173 | INIT_LIST_HEAD(&hd->modules); |
7bc6faac JH |
174 | INIT_LIST_HEAD(&hd->connections); |
175 | ida_init(&hd->cport_id_map); | |
176 | hd->buffer_size_max = buffer_size_max; | |
177 | hd->num_cports = num_cports; | |
178 | ||
d4c80bad JH |
179 | hd->dev.parent = parent; |
180 | hd->dev.bus = &greybus_bus_type; | |
181 | hd->dev.type = &greybus_hd_type; | |
fbbd2b7c | 182 | hd->dev.groups = bus_groups; |
d4c80bad JH |
183 | hd->dev.dma_mask = hd->dev.parent->dma_mask; |
184 | device_initialize(&hd->dev); | |
185 | dev_set_name(&hd->dev, "greybus%d", hd->bus_id); | |
186 | ||
1f79046b AE |
187 | trace_gb_hd_create(hd); |
188 | ||
7adeaae7 JH |
189 | hd->svc = gb_svc_create(hd); |
190 | if (!hd->svc) { | |
191 | dev_err(&hd->dev, "failed to create svc\n"); | |
2c848944 JH |
192 | put_device(&hd->dev); |
193 | return ERR_PTR(-ENOMEM); | |
5ef32384 JH |
194 | } |
195 | ||
2c848944 | 196 | return hd; |
5ef32384 | 197 | } |
2c848944 | 198 | EXPORT_SYMBOL_GPL(gb_hd_create); |
5ef32384 | 199 | |
c1700479 JH |
200 | int gb_hd_add(struct gb_host_device *hd) |
201 | { | |
2adaefb1 JH |
202 | int ret; |
203 | ||
204 | ret = device_add(&hd->dev); | |
205 | if (ret) | |
206 | return ret; | |
207 | ||
7adeaae7 | 208 | ret = gb_svc_add(hd->svc); |
0bf1f244 | 209 | if (ret) { |
0bf1f244 JH |
210 | device_del(&hd->dev); |
211 | return ret; | |
212 | } | |
213 | ||
1f79046b AE |
214 | trace_gb_hd_add(hd); |
215 | ||
c1700479 | 216 | return 0; |
7bc6faac | 217 | } |
c1700479 | 218 | EXPORT_SYMBOL_GPL(gb_hd_add); |
7bc6faac | 219 | |
c1700479 | 220 | void gb_hd_del(struct gb_host_device *hd) |
7bc6faac | 221 | { |
1f79046b AE |
222 | trace_gb_hd_del(hd); |
223 | ||
24988d3a JH |
224 | /* |
225 | * Tear down the svc and flush any on-going hotplug processing before | |
226 | * removing the remaining interfaces. | |
227 | */ | |
7adeaae7 | 228 | gb_svc_del(hd->svc); |
2adaefb1 JH |
229 | |
230 | device_del(&hd->dev); | |
c1700479 JH |
231 | } |
232 | EXPORT_SYMBOL_GPL(gb_hd_del); | |
7bc6faac | 233 | |
1f77b363 DL |
234 | void gb_hd_shutdown(struct gb_host_device *hd) |
235 | { | |
236 | gb_svc_del(hd->svc); | |
237 | } | |
238 | EXPORT_SYMBOL_GPL(gb_hd_shutdown); | |
239 | ||
c1700479 JH |
240 | void gb_hd_put(struct gb_host_device *hd) |
241 | { | |
2adaefb1 | 242 | put_device(&hd->dev); |
7bc6faac | 243 | } |
c1700479 | 244 | EXPORT_SYMBOL_GPL(gb_hd_put); |
2adaefb1 JH |
245 | |
246 | int __init gb_hd_init(void) | |
247 | { | |
248 | ida_init(&gb_hd_bus_id_map); | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | void gb_hd_exit(void) | |
254 | { | |
255 | ida_destroy(&gb_hd_bus_id_map); | |
256 | } |