1 /*-
2 * Copyright (c) 2015 Brian Fundakowski Feldman. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25 #include <sys/cdefs.h>
26 __FBSDID("$FreeBSD: releng/11.1/sys/dev/spibus/spigen.c 311342 2017-01-05 00:26:57Z gonzo $");
27
28 #include <sys/param.h>
29 #include <sys/systm.h>
30 #include <sys/bus.h>
31 #include <sys/conf.h>
32 #include <sys/kernel.h>
33 #include <sys/lock.h>
34 #include <sys/malloc.h>
35 #include <sys/mman.h>
36 #include <sys/mutex.h>
37 #include <sys/module.h>
38 #include <sys/proc.h>
39 #include <sys/rwlock.h>
40 #include <sys/spigenio.h>
41 #include <sys/sysctl.h>
42 #include <sys/types.h>
43
44 #include <vm/vm.h>
45 #include <vm/vm_extern.h>
46 #include <vm/vm_object.h>
47 #include <vm/vm_page.h>
48 #include <vm/vm_pager.h>
49
50 #include <dev/spibus/spi.h>
51
52 #include "spibus_if.h"
53
54 struct spigen_softc {
55 device_t sc_dev;
56 struct cdev *sc_cdev;
57 struct mtx sc_mtx;
58 uint32_t sc_clock_speed;
59 uint32_t sc_command_length_max; /* cannot change while mmapped */
60 uint32_t sc_data_length_max; /* cannot change while mmapped */
61 vm_object_t sc_mmap_buffer; /* command, then data */
62 vm_offset_t sc_mmap_kvaddr;
63 size_t sc_mmap_buffer_size;
64 int sc_mmap_busy;
65 int sc_debug;
66 };
67
68 static int
69 spigen_probe(device_t dev)
70 {
71
72 device_set_desc(dev, "SPI Generic IO");
73
74 return (BUS_PROBE_NOWILDCARD);
75 }
76
77 static int spigen_open(struct cdev *, int, int, struct thread *);
78 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
79 static int spigen_close(struct cdev *, int, int, struct thread *);
80 static d_mmap_single_t spigen_mmap_single;
81
82 static struct cdevsw spigen_cdevsw = {
83 .d_version = D_VERSION,
84 .d_name = "spigen",
85 .d_open = spigen_open,
86 .d_ioctl = spigen_ioctl,
87 .d_mmap_single = spigen_mmap_single,
88 .d_close = spigen_close
89 };
90
91 static int
92 spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
93 {
94 struct spigen_softc *sc = (struct spigen_softc *)arg1;
95 uint32_t command_length_max;
96 int error;
97
98 mtx_lock(&sc->sc_mtx);
99 command_length_max = sc->sc_command_length_max;
100 mtx_unlock(&sc->sc_mtx);
101 error = sysctl_handle_int(oidp, &command_length_max,
102 sizeof(command_length_max), req);
103 if (error == 0 && req->newptr != NULL) {
104 mtx_lock(&sc->sc_mtx);
105 if (sc->sc_mmap_buffer != NULL)
106 error = EBUSY;
107 else
108 sc->sc_command_length_max = command_length_max;
109 mtx_unlock(&sc->sc_mtx);
110 }
111 return (error);
112 }
113
114 static int
115 spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
116 {
117 struct spigen_softc *sc = (struct spigen_softc *)arg1;
118 uint32_t data_length_max;
119 int error;
120
121 mtx_lock(&sc->sc_mtx);
122 data_length_max = sc->sc_data_length_max;
123 mtx_unlock(&sc->sc_mtx);
124 error = sysctl_handle_int(oidp, &data_length_max,
125 sizeof(data_length_max), req);
126 if (error == 0 && req->newptr != NULL) {
127 mtx_lock(&sc->sc_mtx);
128 if (sc->sc_mmap_buffer != NULL)
129 error = EBUSY;
130 else
131 sc->sc_data_length_max = data_length_max;
132 mtx_unlock(&sc->sc_mtx);
133 }
134 return (error);
135 }
136
137 static void
138 spigen_sysctl_init(struct spigen_softc *sc)
139 {
140 struct sysctl_ctx_list *ctx;
141 struct sysctl_oid *tree_node;
142 struct sysctl_oid_list *tree;
143
144 /*
145 * Add system sysctl tree/handlers.
146 */
147 ctx = device_get_sysctl_ctx(sc->sc_dev);
148 tree_node = device_get_sysctl_tree(sc->sc_dev);
149 tree = SYSCTL_CHILDREN(tree_node);
150 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
151 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
152 spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
153 SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
154 CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
155 spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
156 SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
157 &sc->sc_debug, 0, "debug flags");
158
159 }
160
161 static int
162 spigen_attach(device_t dev)
163 {
164 struct spigen_softc *sc;
165 const int unit = device_get_unit(dev);
166
167 sc = device_get_softc(dev);
168 sc->sc_dev = dev;
169 sc->sc_cdev = make_dev(&spigen_cdevsw, unit,
170 UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit);
171 sc->sc_cdev->si_drv1 = dev;
172 sc->sc_command_length_max = PAGE_SIZE;
173 sc->sc_data_length_max = PAGE_SIZE;
174 mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
175 spigen_sysctl_init(sc);
176
177 return (0);
178 }
179
180 static int
181 spigen_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
182 {
183
184 return (0);
185 }
186
187 static int
188 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
189 {
190 struct spi_command transfer = SPI_COMMAND_INITIALIZER;
191 device_t dev = cdev->si_drv1;
192 struct spigen_softc *sc = device_get_softc(dev);
193 int error = 0;
194
195 mtx_lock(&sc->sc_mtx);
196 if (st->st_command.iov_len == 0)
197 error = EINVAL;
198 else if (st->st_command.iov_len > sc->sc_command_length_max ||
199 st->st_data.iov_len > sc->sc_data_length_max)
200 error = ENOMEM;
201 mtx_unlock(&sc->sc_mtx);
202 if (error)
203 return (error);
204
205 #if 0
206 device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
207 st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
208 #endif
209 transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
210 M_DEVBUF, M_WAITOK);
211 if (transfer.tx_cmd == NULL)
212 return (ENOMEM);
213 if (st->st_data.iov_len > 0) {
214 transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
215 M_DEVBUF, M_WAITOK);
216 if (transfer.tx_data == NULL) {
217 free(transfer.tx_cmd, M_DEVBUF);
218 return (ENOMEM);
219 }
220 }
221 else
222 transfer.tx_data = transfer.rx_data = NULL;
223
224 error = copyin(st->st_command.iov_base, transfer.tx_cmd,
225 transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
226 if ((error == 0) && (st->st_data.iov_len > 0))
227 error = copyin(st->st_data.iov_base, transfer.tx_data,
228 transfer.tx_data_sz = transfer.rx_data_sz =
229 st->st_data.iov_len);
230 if (error == 0)
231 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
232 if (error == 0) {
233 error = copyout(transfer.rx_cmd, st->st_command.iov_base,
234 transfer.rx_cmd_sz);
235 if ((error == 0) && (st->st_data.iov_len > 0))
236 error = copyout(transfer.rx_data, st->st_data.iov_base,
237 transfer.rx_data_sz);
238 }
239
240 free(transfer.tx_cmd, M_DEVBUF);
241 free(transfer.tx_data, M_DEVBUF);
242 return (error);
243 }
244
245 static int
246 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
247 {
248 struct spi_command transfer = SPI_COMMAND_INITIALIZER;
249 device_t dev = cdev->si_drv1;
250 struct spigen_softc *sc = device_get_softc(dev);
251 int error = 0;
252
253 mtx_lock(&sc->sc_mtx);
254 if (sc->sc_mmap_busy)
255 error = EBUSY;
256 else if (stm->stm_command_length > sc->sc_command_length_max ||
257 stm->stm_data_length > sc->sc_data_length_max)
258 error = E2BIG;
259 else if (sc->sc_mmap_buffer == NULL)
260 error = EINVAL;
261 else if (sc->sc_mmap_buffer_size <
262 stm->stm_command_length + stm->stm_data_length)
263 error = ENOMEM;
264 if (error == 0)
265 sc->sc_mmap_busy = 1;
266 mtx_unlock(&sc->sc_mtx);
267 if (error)
268 return (error);
269
270 transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
271 transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
272 transfer.tx_data = transfer.rx_data =
273 (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
274 transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
275 error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
276
277 mtx_lock(&sc->sc_mtx);
278 KASSERT(sc->sc_mmap_busy, ("mmap no longer marked busy"));
279 sc->sc_mmap_busy = 0;
280 mtx_unlock(&sc->sc_mtx);
281 return (error);
282 }
283
284 static int
285 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
286 struct thread *td)
287 {
288 device_t dev = cdev->si_drv1;
289 struct spigen_softc *sc = device_get_softc(dev);
290 int error;
291
292 switch (cmd) {
293 case SPIGENIOC_TRANSFER:
294 error = spigen_transfer(cdev, (struct spigen_transfer *)data);
295 break;
296 case SPIGENIOC_TRANSFER_MMAPPED:
297 error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
298 break;
299 case SPIGENIOC_GET_CLOCK_SPEED:
300 mtx_lock(&sc->sc_mtx);
301 *(uint32_t *)data = sc->sc_clock_speed;
302 /* XXX TODO: implement spibus ivar call */
303 mtx_unlock(&sc->sc_mtx);
304 error = 0;
305 break;
306 case SPIGENIOC_SET_CLOCK_SPEED:
307 mtx_lock(&sc->sc_mtx);
308 sc->sc_clock_speed = *(uint32_t *)data;
309 mtx_unlock(&sc->sc_mtx);
310 error = 0;
311 break;
312 default:
313 error = EOPNOTSUPP;
314 }
315 return (error);
316 }
317
318 static int
319 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
320 vm_size_t size, struct vm_object **object, int nprot)
321 {
322 device_t dev = cdev->si_drv1;
323 struct spigen_softc *sc = device_get_softc(dev);
324 vm_page_t *m;
325 size_t n, pages;
326
327 if (size == 0 ||
328 (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
329 != (PROT_READ | PROT_WRITE))
330 return (EINVAL);
331 size = roundup2(size, PAGE_SIZE);
332 pages = size / PAGE_SIZE;
333
334 mtx_lock(&sc->sc_mtx);
335 if (sc->sc_mmap_buffer != NULL) {
336 mtx_unlock(&sc->sc_mtx);
337 return (EBUSY);
338 } else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
339 mtx_unlock(&sc->sc_mtx);
340 return (E2BIG);
341 }
342 sc->sc_mmap_buffer_size = size;
343 *offset = 0;
344 sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
345 nprot, *offset, curthread->td_ucred);
346 m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
347 VM_OBJECT_WLOCK(*object);
348 vm_object_reference_locked(*object); // kernel and userland both
349 for (n = 0; n < pages; n++) {
350 m[n] = vm_page_grab(*object, n,
351 VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
352 m[n]->valid = VM_PAGE_BITS_ALL;
353 }
354 VM_OBJECT_WUNLOCK(*object);
355 sc->sc_mmap_kvaddr = kva_alloc(size);
356 pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
357 free(m, M_TEMP);
358 mtx_unlock(&sc->sc_mtx);
359
360 if (*object == NULL)
361 return (EINVAL);
362 return (0);
363 }
364
365 static int
366 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
367 {
368 device_t dev = cdev->si_drv1;
369 struct spigen_softc *sc = device_get_softc(dev);
370
371 mtx_lock(&sc->sc_mtx);
372 if (sc->sc_mmap_buffer != NULL) {
373 pmap_qremove(sc->sc_mmap_kvaddr,
374 sc->sc_mmap_buffer_size / PAGE_SIZE);
375 kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
376 sc->sc_mmap_kvaddr = 0;
377 vm_object_deallocate(sc->sc_mmap_buffer);
378 sc->sc_mmap_buffer = NULL;
379 sc->sc_mmap_buffer_size = 0;
380 }
381 mtx_unlock(&sc->sc_mtx);
382 return (0);
383 }
384
385 static int
386 spigen_detach(device_t dev)
387 {
388
389 return (EIO);
390 }
391
392 static devclass_t spigen_devclass;
393
394 static device_method_t spigen_methods[] = {
395 /* Device interface */
396 DEVMETHOD(device_probe, spigen_probe),
397 DEVMETHOD(device_attach, spigen_attach),
398 DEVMETHOD(device_detach, spigen_detach),
399
400 { 0, 0 }
401 };
402
403 static driver_t spigen_driver = {
404 "spigen",
405 spigen_methods,
406 sizeof(struct spigen_softc),
407 };
408
409 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
Cache object: ec0e387e9b10d9efea5a9409bf4577eb
|