1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Greybus Host Device |
4 | * |
5 | * Copyright 2014-2015 Google Inc. |
6 | * Copyright 2014-2015 Linaro Ltd. |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/greybus.h> |
12 | |
13 | #include "greybus_trace.h" |
14 | |
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); |
19 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_in); |
20 | EXPORT_TRACEPOINT_SYMBOL_GPL(gb_message_submit); |
21 | |
22 | static struct ida gb_hd_bus_id_map; |
23 | |
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 | |
33 | static ssize_t bus_id_show(struct device *dev, |
34 | struct device_attribute *attr, char *buf) |
35 | { |
36 | struct gb_host_device *hd = to_gb_host_device(dev); |
37 | |
38 | return sprintf(buf, fmt: "%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 | |
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_alloc_range(id_map, min: cport_id, max: cport_id, 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 | |
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_free(id_map, id: cport_id); |
68 | } |
69 | EXPORT_SYMBOL_GPL(gb_hd_cport_release_reserved); |
70 | |
71 | /* Locking: Caller guarantees serialisation */ |
72 | int gb_hd_cport_allocate(struct gb_host_device *hd, int cport_id, |
73 | unsigned long flags) |
74 | { |
75 | struct ida *id_map = &hd->cport_id_map; |
76 | int ida_start, ida_end; |
77 | |
78 | if (hd->driver->cport_allocate) |
79 | return hd->driver->cport_allocate(hd, cport_id, flags); |
80 | |
81 | if (cport_id < 0) { |
82 | ida_start = 0; |
83 | ida_end = hd->num_cports - 1; |
84 | } else if (cport_id < hd->num_cports) { |
85 | ida_start = cport_id; |
86 | ida_end = cport_id; |
87 | } else { |
88 | dev_err(&hd->dev, "cport %d not available\n" , cport_id); |
89 | return -EINVAL; |
90 | } |
91 | |
92 | return ida_alloc_range(id_map, min: ida_start, max: 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 | { |
98 | if (hd->driver->cport_release) { |
99 | hd->driver->cport_release(hd, cport_id); |
100 | return; |
101 | } |
102 | |
103 | ida_free(&hd->cport_id_map, id: cport_id); |
104 | } |
105 | |
106 | static void gb_hd_release(struct device *dev) |
107 | { |
108 | struct gb_host_device *hd = to_gb_host_device(dev); |
109 | |
110 | trace_gb_hd_release(hd); |
111 | |
112 | if (hd->svc) |
113 | gb_svc_put(svc: hd->svc); |
114 | ida_free(&gb_hd_bus_id_map, id: hd->bus_id); |
115 | ida_destroy(ida: &hd->cport_id_map); |
116 | kfree(objp: hd); |
117 | } |
118 | |
119 | const struct device_type greybus_hd_type = { |
120 | .name = "greybus_host_device" , |
121 | .release = gb_hd_release, |
122 | }; |
123 | |
124 | struct gb_host_device *gb_hd_create(struct gb_hd_driver *driver, |
125 | struct device *parent, |
126 | size_t buffer_size_max, |
127 | size_t num_cports) |
128 | { |
129 | struct gb_host_device *hd; |
130 | int ret; |
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)) { |
137 | dev_err(parent, "mandatory hd-callbacks missing\n" ); |
138 | return ERR_PTR(error: -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(error: -EINVAL); |
144 | } |
145 | |
146 | if (num_cports == 0 || num_cports > CPORT_ID_MAX + 1) { |
147 | dev_err(parent, "Invalid number of CPorts: %zu\n" , num_cports); |
148 | return ERR_PTR(error: -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(size: sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL); |
162 | if (!hd) |
163 | return ERR_PTR(error: -ENOMEM); |
164 | |
165 | ret = ida_alloc_min(ida: &gb_hd_bus_id_map, min: 1, GFP_KERNEL); |
166 | if (ret < 0) { |
167 | kfree(objp: hd); |
168 | return ERR_PTR(error: ret); |
169 | } |
170 | hd->bus_id = ret; |
171 | |
172 | hd->driver = driver; |
173 | INIT_LIST_HEAD(list: &hd->modules); |
174 | INIT_LIST_HEAD(list: &hd->connections); |
175 | ida_init(ida: &hd->cport_id_map); |
176 | hd->buffer_size_max = buffer_size_max; |
177 | hd->num_cports = num_cports; |
178 | |
179 | hd->dev.parent = parent; |
180 | hd->dev.bus = &greybus_bus_type; |
181 | hd->dev.type = &greybus_hd_type; |
182 | hd->dev.groups = bus_groups; |
183 | hd->dev.dma_mask = hd->dev.parent->dma_mask; |
184 | device_initialize(dev: &hd->dev); |
185 | dev_set_name(dev: &hd->dev, name: "greybus%d" , hd->bus_id); |
186 | |
187 | trace_gb_hd_create(hd); |
188 | |
189 | hd->svc = gb_svc_create(hd); |
190 | if (!hd->svc) { |
191 | dev_err(&hd->dev, "failed to create svc\n" ); |
192 | put_device(dev: &hd->dev); |
193 | return ERR_PTR(error: -ENOMEM); |
194 | } |
195 | |
196 | return hd; |
197 | } |
198 | EXPORT_SYMBOL_GPL(gb_hd_create); |
199 | |
200 | int gb_hd_add(struct gb_host_device *hd) |
201 | { |
202 | int ret; |
203 | |
204 | ret = device_add(dev: &hd->dev); |
205 | if (ret) |
206 | return ret; |
207 | |
208 | ret = gb_svc_add(svc: hd->svc); |
209 | if (ret) { |
210 | device_del(dev: &hd->dev); |
211 | return ret; |
212 | } |
213 | |
214 | trace_gb_hd_add(hd); |
215 | |
216 | return 0; |
217 | } |
218 | EXPORT_SYMBOL_GPL(gb_hd_add); |
219 | |
220 | void gb_hd_del(struct gb_host_device *hd) |
221 | { |
222 | trace_gb_hd_del(hd); |
223 | |
224 | /* |
225 | * Tear down the svc and flush any on-going hotplug processing before |
226 | * removing the remaining interfaces. |
227 | */ |
228 | gb_svc_del(svc: hd->svc); |
229 | |
230 | device_del(dev: &hd->dev); |
231 | } |
232 | EXPORT_SYMBOL_GPL(gb_hd_del); |
233 | |
234 | void gb_hd_shutdown(struct gb_host_device *hd) |
235 | { |
236 | gb_svc_del(svc: hd->svc); |
237 | } |
238 | EXPORT_SYMBOL_GPL(gb_hd_shutdown); |
239 | |
240 | void gb_hd_put(struct gb_host_device *hd) |
241 | { |
242 | put_device(dev: &hd->dev); |
243 | } |
244 | EXPORT_SYMBOL_GPL(gb_hd_put); |
245 | |
246 | int __init gb_hd_init(void) |
247 | { |
248 | ida_init(ida: &gb_hd_bus_id_map); |
249 | |
250 | return 0; |
251 | } |
252 | |
253 | void gb_hd_exit(void) |
254 | { |
255 | ida_destroy(ida: &gb_hd_bus_id_map); |
256 | } |
257 | |