Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/greybus/hd.c
26278 views
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, "%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, cport_id, 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, 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, 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
{
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, 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(hd->svc);
114
ida_free(&gb_hd_bus_id_map, hd->bus_id);
115
ida_destroy(&hd->cport_id_map);
116
kfree(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(-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
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(-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
165
ret = ida_alloc_min(&gb_hd_bus_id_map, 1, GFP_KERNEL);
166
if (ret < 0) {
167
kfree(hd);
168
return ERR_PTR(ret);
169
}
170
hd->bus_id = ret;
171
172
hd->driver = driver;
173
INIT_LIST_HEAD(&hd->modules);
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
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(&hd->dev);
185
dev_set_name(&hd->dev, "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(&hd->dev);
193
return ERR_PTR(-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(&hd->dev);
205
if (ret)
206
return ret;
207
208
ret = gb_svc_add(hd->svc);
209
if (ret) {
210
device_del(&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(hd->svc);
229
230
device_del(&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(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(&hd->dev);
243
}
244
EXPORT_SYMBOL_GPL(gb_hd_put);
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
}
257
258