xref: /freebsd/sys/dev/spibus/spigen.c (revision 964219664dcec4198441910904fb9064569d174d)
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$");
27 
28 #include "opt_platform.h"
29 #include "opt_spi.h"
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/conf.h>
35 #include <sys/kernel.h>
36 #include <sys/lock.h>
37 #include <sys/malloc.h>
38 #include <sys/mman.h>
39 #include <sys/mutex.h>
40 #include <sys/module.h>
41 #include <sys/proc.h>
42 #include <sys/rwlock.h>
43 #include <sys/spigenio.h>
44 #include <sys/sysctl.h>
45 #include <sys/types.h>
46 
47 #include <vm/vm.h>
48 #include <vm/vm_extern.h>
49 #include <vm/vm_object.h>
50 #include <vm/vm_page.h>
51 #include <vm/vm_pager.h>
52 
53 #include <dev/spibus/spi.h>
54 #include <dev/spibus/spibusvar.h>
55 
56 #ifdef FDT
57 #include <dev/ofw/ofw_bus_subr.h>
58 #endif
59 
60 #include "spibus_if.h"
61 
62 #define	SPIGEN_OPEN		(1 << 0)
63 #define	SPIGEN_MMAP_BUSY	(1 << 1)
64 
65 struct spigen_softc {
66 	device_t sc_dev;
67 	struct cdev *sc_cdev;
68 #ifdef SPIGEN_LEGACY_CDEVNAME
69 	struct cdev *sc_adev;           /* alias device */
70 #endif
71 	struct mtx sc_mtx;
72 	uint32_t sc_command_length_max; /* cannot change while mmapped */
73 	uint32_t sc_data_length_max;    /* cannot change while mmapped */
74 	vm_object_t sc_mmap_buffer;     /* command, then data */
75 	vm_offset_t sc_mmap_kvaddr;
76 	size_t sc_mmap_buffer_size;
77 	int sc_debug;
78 	int sc_flags;
79 };
80 
81 static int
82 spigen_probe(device_t dev)
83 {
84 	int rv;
85 
86 	/*
87 	 * By default we only bid to attach if specifically added by our parent
88 	 * (usually via hint.spigen.#.at=busname).  On FDT systems we bid as the
89 	 * default driver based on being configured in the FDT data.
90 	 */
91 	rv = BUS_PROBE_NOWILDCARD;
92 
93 #ifdef FDT
94 	if (ofw_bus_status_okay(dev) &&
95 	    ofw_bus_is_compatible(dev, "freebsd,spigen"))
96                 rv = BUS_PROBE_DEFAULT;
97 #endif
98 
99 	device_set_desc(dev, "SPI Generic IO");
100 
101 	return (rv);
102 }
103 
104 static int spigen_open(struct cdev *, int, int, struct thread *);
105 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
106 static int spigen_close(struct cdev *, int, int, struct thread *);
107 static d_mmap_single_t spigen_mmap_single;
108 
109 static struct cdevsw spigen_cdevsw = {
110 	.d_version =     D_VERSION,
111 	.d_name =        "spigen",
112 	.d_open =        spigen_open,
113 	.d_ioctl =       spigen_ioctl,
114 	.d_mmap_single = spigen_mmap_single,
115 	.d_close =       spigen_close
116 };
117 
118 static int
119 spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
120 {
121 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
122 	uint32_t command_length_max;
123 	int error;
124 
125 	mtx_lock(&sc->sc_mtx);
126 	command_length_max = sc->sc_command_length_max;
127 	mtx_unlock(&sc->sc_mtx);
128 	error = sysctl_handle_int(oidp, &command_length_max,
129 	    sizeof(command_length_max), req);
130 	if (error == 0 && req->newptr != NULL) {
131 		mtx_lock(&sc->sc_mtx);
132 		if (sc->sc_mmap_buffer != NULL)
133 			error = EBUSY;
134 		else
135 			sc->sc_command_length_max = command_length_max;
136 		mtx_unlock(&sc->sc_mtx);
137 	}
138 	return (error);
139 }
140 
141 static int
142 spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
143 {
144 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
145 	uint32_t data_length_max;
146 	int error;
147 
148 	mtx_lock(&sc->sc_mtx);
149 	data_length_max = sc->sc_data_length_max;
150 	mtx_unlock(&sc->sc_mtx);
151 	error = sysctl_handle_int(oidp, &data_length_max,
152 	    sizeof(data_length_max), req);
153 	if (error == 0 && req->newptr != NULL) {
154 		mtx_lock(&sc->sc_mtx);
155 		if (sc->sc_mmap_buffer != NULL)
156 			error = EBUSY;
157 		else
158 			sc->sc_data_length_max = data_length_max;
159 		mtx_unlock(&sc->sc_mtx);
160 	}
161 	return (error);
162 }
163 
164 static void
165 spigen_sysctl_init(struct spigen_softc *sc)
166 {
167 	struct sysctl_ctx_list *ctx;
168 	struct sysctl_oid *tree_node;
169 	struct sysctl_oid_list *tree;
170 
171 	/*
172 	 * Add system sysctl tree/handlers.
173 	 */
174 	ctx = device_get_sysctl_ctx(sc->sc_dev);
175 	tree_node = device_get_sysctl_tree(sc->sc_dev);
176 	tree = SYSCTL_CHILDREN(tree_node);
177 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
178 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
179 	    spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
180 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
181 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
182 	    spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
183 	SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
184 	    &sc->sc_debug, 0, "debug flags");
185 
186 }
187 
188 static int
189 spigen_attach(device_t dev)
190 {
191 	struct spigen_softc *sc;
192 	const int unit = device_get_unit(dev);
193 	int cs, res;
194 	struct make_dev_args mda;
195 
196 	spibus_get_cs(dev, &cs);
197 	cs &= ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */
198 
199 	sc = device_get_softc(dev);
200 	sc->sc_dev = dev;
201 	sc->sc_command_length_max = PAGE_SIZE;
202 	sc->sc_data_length_max = PAGE_SIZE;
203 
204 	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
205 
206 	make_dev_args_init(&mda);
207 	mda.mda_flags = MAKEDEV_WAITOK;
208 	mda.mda_devsw = &spigen_cdevsw;
209 	mda.mda_cr = NULL;
210 	mda.mda_uid = UID_ROOT;
211 	mda.mda_gid = GID_OPERATOR;
212 	mda.mda_mode = 0660;
213 	mda.mda_unit = unit;
214 	mda.mda_si_drv1 = dev;
215 
216 	res = make_dev_s(&mda, &(sc->sc_cdev), "spigen%d.%d",
217 	    device_get_unit(device_get_parent(dev)), cs);
218 	if (res) {
219 		return res;
220 	}
221 
222 #ifdef SPIGEN_LEGACY_CDEVNAME
223 	res = make_dev_alias_p(0, &sc->sc_adev, sc->sc_cdev, "spigen%d", unit);
224 	if (res) {
225 		if (sc->sc_cdev) {
226 			destroy_dev(sc->sc_cdev);
227 			sc->sc_cdev = NULL;
228 		}
229 		return res;
230 	}
231 #endif
232 
233 	spigen_sysctl_init(sc);
234 
235 	return (0);
236 }
237 
238 static int
239 spigen_open(struct cdev *cdev, int oflags, int devtype, struct thread *td)
240 {
241 	int error;
242 	device_t dev;
243 	struct spigen_softc *sc;
244 
245 	error = 0;
246 	dev = cdev->si_drv1;
247 	sc = device_get_softc(dev);
248 
249 	mtx_lock(&sc->sc_mtx);
250 	if (sc->sc_flags & SPIGEN_OPEN)
251 		error = EBUSY;
252 	else
253 		sc->sc_flags |= SPIGEN_OPEN;
254 	mtx_unlock(&sc->sc_mtx);
255 
256 	return (error);
257 }
258 
259 static int
260 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
261 {
262 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
263 	device_t dev = cdev->si_drv1;
264 	struct spigen_softc *sc = device_get_softc(dev);
265 	int error = 0;
266 
267 	mtx_lock(&sc->sc_mtx);
268 	if (st->st_command.iov_len == 0)
269 		error = EINVAL;
270 	else if (st->st_command.iov_len > sc->sc_command_length_max ||
271 	    st->st_data.iov_len > sc->sc_data_length_max)
272 		error = ENOMEM;
273 	mtx_unlock(&sc->sc_mtx);
274 	if (error)
275 		return (error);
276 
277 #if 0
278 	device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
279 	    st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
280 #endif
281 	transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
282 	    M_DEVBUF, M_WAITOK);
283 	if (st->st_data.iov_len > 0) {
284 		transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
285 		    M_DEVBUF, M_WAITOK);
286 	}
287 	else
288 		transfer.tx_data = transfer.rx_data = NULL;
289 
290 	error = copyin(st->st_command.iov_base, transfer.tx_cmd,
291 	    transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
292 	if ((error == 0) && (st->st_data.iov_len > 0))
293 		error = copyin(st->st_data.iov_base, transfer.tx_data,
294 		    transfer.tx_data_sz = transfer.rx_data_sz =
295 		                          st->st_data.iov_len);
296 	if (error == 0)
297 		error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
298 	if (error == 0) {
299 		error = copyout(transfer.rx_cmd, st->st_command.iov_base,
300 		    transfer.rx_cmd_sz);
301 		if ((error == 0) && (st->st_data.iov_len > 0))
302 			error = copyout(transfer.rx_data, st->st_data.iov_base,
303 			    transfer.rx_data_sz);
304 	}
305 
306 	free(transfer.tx_cmd, M_DEVBUF);
307 	free(transfer.tx_data, M_DEVBUF);
308 	return (error);
309 }
310 
311 static int
312 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
313 {
314 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
315 	device_t dev = cdev->si_drv1;
316 	struct spigen_softc *sc = device_get_softc(dev);
317 	int error = 0;
318 
319 	mtx_lock(&sc->sc_mtx);
320 	if (sc->sc_flags & SPIGEN_MMAP_BUSY)
321 		error = EBUSY;
322 	else if (stm->stm_command_length > sc->sc_command_length_max ||
323 	    stm->stm_data_length > sc->sc_data_length_max)
324 		error = E2BIG;
325 	else if (sc->sc_mmap_buffer == NULL)
326 		error = EINVAL;
327 	else if (sc->sc_mmap_buffer_size <
328 	    stm->stm_command_length + stm->stm_data_length)
329 		error = ENOMEM;
330 	if (error == 0)
331 		sc->sc_flags |= SPIGEN_MMAP_BUSY;
332 	mtx_unlock(&sc->sc_mtx);
333 	if (error)
334 		return (error);
335 
336 	transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
337 	transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
338 	transfer.tx_data = transfer.rx_data =
339 	    (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
340 	transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
341 	error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
342 
343 	mtx_lock(&sc->sc_mtx);
344 	KASSERT((sc->sc_flags & SPIGEN_MMAP_BUSY), ("mmap no longer marked busy"));
345 	sc->sc_flags &= ~(SPIGEN_MMAP_BUSY);
346 	mtx_unlock(&sc->sc_mtx);
347 	return (error);
348 }
349 
350 static int
351 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
352     struct thread *td)
353 {
354 	device_t dev = cdev->si_drv1;
355 	int error;
356 
357 	switch (cmd) {
358 	case SPIGENIOC_TRANSFER:
359 		error = spigen_transfer(cdev, (struct spigen_transfer *)data);
360 		break;
361 	case SPIGENIOC_TRANSFER_MMAPPED:
362 		error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
363 		break;
364 	case SPIGENIOC_GET_CLOCK_SPEED:
365 		error = spibus_get_clock(dev, (uint32_t *)data);
366 		break;
367 	case SPIGENIOC_SET_CLOCK_SPEED:
368 		error = spibus_set_clock(dev, *(uint32_t *)data);
369 		break;
370 	case SPIGENIOC_GET_SPI_MODE:
371 		error = spibus_get_mode(dev, (uint32_t *)data);
372 		break;
373 	case SPIGENIOC_SET_SPI_MODE:
374 		error = spibus_set_mode(dev, *(uint32_t *)data);
375 		break;
376 	default:
377 		error = ENOTTY;
378 		break;
379 	}
380 	return (error);
381 }
382 
383 static int
384 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
385     vm_size_t size, struct vm_object **object, int nprot)
386 {
387 	device_t dev = cdev->si_drv1;
388 	struct spigen_softc *sc = device_get_softc(dev);
389 	vm_page_t *m;
390 	size_t n, pages;
391 
392 	if (size == 0 ||
393 	    (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
394 	    != (PROT_READ | PROT_WRITE))
395 		return (EINVAL);
396 	size = roundup2(size, PAGE_SIZE);
397 	pages = size / PAGE_SIZE;
398 
399 	mtx_lock(&sc->sc_mtx);
400 	if (sc->sc_mmap_buffer != NULL) {
401 		mtx_unlock(&sc->sc_mtx);
402 		return (EBUSY);
403 	} else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
404 		mtx_unlock(&sc->sc_mtx);
405 		return (E2BIG);
406 	}
407 	sc->sc_mmap_buffer_size = size;
408 	*offset = 0;
409 	sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
410 	    nprot, *offset, curthread->td_ucred);
411 	m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
412 	VM_OBJECT_WLOCK(*object);
413 	vm_object_reference_locked(*object); // kernel and userland both
414 	for (n = 0; n < pages; n++) {
415 		m[n] = vm_page_grab(*object, n,
416 		    VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
417 		m[n]->valid = VM_PAGE_BITS_ALL;
418 	}
419 	VM_OBJECT_WUNLOCK(*object);
420 	sc->sc_mmap_kvaddr = kva_alloc(size);
421 	pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
422 	free(m, M_TEMP);
423 	mtx_unlock(&sc->sc_mtx);
424 
425 	if (*object == NULL)
426 		 return (EINVAL);
427 	return (0);
428 }
429 
430 static int
431 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
432 {
433 	device_t dev = cdev->si_drv1;
434 	struct spigen_softc *sc = device_get_softc(dev);
435 
436 	mtx_lock(&sc->sc_mtx);
437 	if (sc->sc_mmap_buffer != NULL) {
438 		pmap_qremove(sc->sc_mmap_kvaddr,
439 		    sc->sc_mmap_buffer_size / PAGE_SIZE);
440 		kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
441 		sc->sc_mmap_kvaddr = 0;
442 		vm_object_deallocate(sc->sc_mmap_buffer);
443 		sc->sc_mmap_buffer = NULL;
444 		sc->sc_mmap_buffer_size = 0;
445 	}
446 	sc->sc_flags &= ~(SPIGEN_OPEN);
447 	mtx_unlock(&sc->sc_mtx);
448 	return (0);
449 }
450 
451 static int
452 spigen_detach(device_t dev)
453 {
454 	struct spigen_softc *sc;
455 
456 	sc = device_get_softc(dev);
457 
458 	mtx_lock(&sc->sc_mtx);
459 	if (sc->sc_flags & SPIGEN_OPEN) {
460 		mtx_unlock(&sc->sc_mtx);
461 		return (EBUSY);
462 	}
463 	mtx_unlock(&sc->sc_mtx);
464 
465 	mtx_destroy(&sc->sc_mtx);
466 
467 #ifdef SPIGEN_LEGACY_CDEVNAME
468 	if (sc->sc_adev)
469 		destroy_dev(sc->sc_adev);
470 #endif
471 
472 	if (sc->sc_cdev)
473 		destroy_dev(sc->sc_cdev);
474 
475 	return (0);
476 }
477 
478 static devclass_t spigen_devclass;
479 
480 static device_method_t spigen_methods[] = {
481 	/* Device interface */
482 	DEVMETHOD(device_probe,		spigen_probe),
483 	DEVMETHOD(device_attach,	spigen_attach),
484 	DEVMETHOD(device_detach,	spigen_detach),
485 
486 	{ 0, 0 }
487 };
488 
489 static driver_t spigen_driver = {
490 	"spigen",
491 	spigen_methods,
492 	sizeof(struct spigen_softc),
493 };
494 
495 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
496 MODULE_DEPEND(spigen, spibus, 1, 1, 1);
497