xref: /freebsd/sys/dev/spibus/spigen.c (revision 4cc3366de27dc5bfcf44c3b596ba850001e33933)
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 
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/bus.h>
33 #include <sys/conf.h>
34 #include <sys/kernel.h>
35 #include <sys/lock.h>
36 #include <sys/malloc.h>
37 #include <sys/mman.h>
38 #include <sys/mutex.h>
39 #include <sys/module.h>
40 #include <sys/proc.h>
41 #include <sys/rwlock.h>
42 #include <sys/spigenio.h>
43 #include <sys/sysctl.h>
44 #include <sys/types.h>
45 
46 #include <vm/vm.h>
47 #include <vm/vm_extern.h>
48 #include <vm/vm_object.h>
49 #include <vm/vm_page.h>
50 #include <vm/vm_pager.h>
51 
52 #include <dev/spibus/spi.h>
53 
54 #include "spibus_if.h"
55 
56 struct spigen_softc {
57 	device_t sc_dev;
58 	struct cdev *sc_cdev;
59 	struct mtx sc_mtx;
60 	uint32_t sc_clock_speed;
61 	uint32_t sc_command_length_max; /* cannot change while mmapped */
62 	uint32_t sc_data_length_max;    /* cannot change while mmapped */
63 	vm_object_t sc_mmap_buffer;     /* command, then data */
64 	vm_offset_t sc_mmap_kvaddr;
65 	size_t sc_mmap_buffer_size;
66 	int sc_mmap_busy;
67 	int sc_debug;
68 };
69 
70 #ifdef FDT
71 static void
72 spigen_identify(driver_t *driver, device_t parent)
73 {
74 	if (device_find_child(parent, "spigen", -1) != NULL)
75 		return;
76 	if (BUS_ADD_CHILD(parent, 0, "spigen", -1) == NULL)
77 		device_printf(parent, "add child failed\n");
78 }
79 #endif
80 
81 static int
82 spigen_probe(device_t dev)
83 {
84 	device_set_desc(dev, "SPI Generic IO");
85 	return (0);
86 }
87 
88 static int spigen_open(struct cdev *, int, int, struct thread *);
89 static int spigen_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
90 static int spigen_close(struct cdev *, int, int, struct thread *);
91 static d_mmap_single_t spigen_mmap_single;
92 
93 static struct cdevsw spigen_cdevsw = {
94 	.d_version =     D_VERSION,
95 	.d_name =        "spigen",
96 	.d_open =        spigen_open,
97 	.d_ioctl =       spigen_ioctl,
98 	.d_mmap_single = spigen_mmap_single,
99 	.d_close =       spigen_close
100 };
101 
102 static int
103 spigen_command_length_max_proc(SYSCTL_HANDLER_ARGS)
104 {
105 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
106 	uint32_t command_length_max;
107 	int error;
108 
109 	mtx_lock(&sc->sc_mtx);
110 	command_length_max = sc->sc_command_length_max;
111 	mtx_unlock(&sc->sc_mtx);
112 	error = sysctl_handle_int(oidp, &command_length_max,
113 	    sizeof(command_length_max), req);
114 	if (error == 0 && req->newptr != NULL) {
115 		mtx_lock(&sc->sc_mtx);
116 		if (sc->sc_mmap_buffer != NULL)
117 			error = EBUSY;
118 		else
119 			sc->sc_command_length_max = command_length_max;
120 		mtx_unlock(&sc->sc_mtx);
121 	}
122 	return (error);
123 }
124 
125 static int
126 spigen_data_length_max_proc(SYSCTL_HANDLER_ARGS)
127 {
128 	struct spigen_softc *sc = (struct spigen_softc *)arg1;
129 	uint32_t data_length_max;
130 	int error;
131 
132 	mtx_lock(&sc->sc_mtx);
133 	data_length_max = sc->sc_data_length_max;
134 	mtx_unlock(&sc->sc_mtx);
135 	error = sysctl_handle_int(oidp, &data_length_max,
136 	    sizeof(data_length_max), req);
137 	if (error == 0 && req->newptr != NULL) {
138 		mtx_lock(&sc->sc_mtx);
139 		if (sc->sc_mmap_buffer != NULL)
140 			error = EBUSY;
141 		else
142 			sc->sc_data_length_max = data_length_max;
143 		mtx_unlock(&sc->sc_mtx);
144 	}
145 	return (error);
146 }
147 
148 static void
149 spigen_sysctl_init(struct spigen_softc *sc)
150 {
151 	struct sysctl_ctx_list *ctx;
152 	struct sysctl_oid *tree_node;
153 	struct sysctl_oid_list *tree;
154 
155 	/*
156 	 * Add system sysctl tree/handlers.
157 	 */
158 	ctx = device_get_sysctl_ctx(sc->sc_dev);
159 	tree_node = device_get_sysctl_tree(sc->sc_dev);
160 	tree = SYSCTL_CHILDREN(tree_node);
161 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "command_length_max",
162 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
163 	    spigen_command_length_max_proc, "IU", "SPI command header portion (octets)");
164 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "data_length_max",
165 	    CTLFLAG_MPSAFE | CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc),
166 	    spigen_data_length_max_proc, "IU", "SPI data trailer portion (octets)");
167 	SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "data", CTLFLAG_RW,
168 	    &sc->sc_debug, 0, "debug flags");
169 
170 }
171 
172 static int
173 spigen_attach(device_t dev)
174 {
175 	struct spigen_softc *sc;
176 	const int unit = device_get_unit(dev);
177 
178 	sc = device_get_softc(dev);
179 	sc->sc_dev = dev;
180 	sc->sc_cdev = make_dev(&spigen_cdevsw, unit,
181 	    UID_ROOT, GID_OPERATOR, 0660, "spigen%d", unit);
182 	sc->sc_cdev->si_drv1 = dev;
183 	sc->sc_command_length_max = PAGE_SIZE;
184 	sc->sc_data_length_max = PAGE_SIZE;
185 	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
186 	spigen_sysctl_init(sc);
187 
188 	return (0);
189 }
190 
191 static int
192 spigen_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
193 {
194 
195 	return (0);
196 }
197 
198 static int
199 spigen_transfer(struct cdev *cdev, struct spigen_transfer *st)
200 {
201 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
202 	device_t dev = cdev->si_drv1;
203 	struct spigen_softc *sc = device_get_softc(dev);
204 	int error = 0;
205 
206 	mtx_lock(&sc->sc_mtx);
207 	if (st->st_command.iov_len == 0)
208 		error = EINVAL;
209 	else if (st->st_command.iov_len > sc->sc_command_length_max ||
210 	    st->st_data.iov_len > sc->sc_data_length_max)
211 		error = ENOMEM;
212 	mtx_unlock(&sc->sc_mtx);
213 	if (error)
214 		return (error);
215 
216 #if 0
217 	device_printf(dev, "cmd %p %u data %p %u\n", st->st_command.iov_base,
218 	    st->st_command.iov_len, st->st_data.iov_base, st->st_data.iov_len);
219 #endif
220 	transfer.tx_cmd = transfer.rx_cmd = malloc(st->st_command.iov_len,
221 	    M_DEVBUF, M_WAITOK);
222 	if (transfer.tx_cmd == NULL)
223 		return (ENOMEM);
224 	if (st->st_data.iov_len > 0) {
225 		transfer.tx_data = transfer.rx_data = malloc(st->st_data.iov_len,
226 		    M_DEVBUF, M_WAITOK);
227 		if (transfer.tx_data == NULL) {
228 			free(transfer.tx_cmd, M_DEVBUF);
229 			return (ENOMEM);
230 		}
231 	}
232 	else
233 		transfer.tx_data = transfer.rx_data = NULL;
234 
235 	error = copyin(st->st_command.iov_base, transfer.tx_cmd,
236 	    transfer.tx_cmd_sz = transfer.rx_cmd_sz = st->st_command.iov_len);
237 	if ((error == 0) && (st->st_data.iov_len > 0))
238 		error = copyin(st->st_data.iov_base, transfer.tx_data,
239 		    transfer.tx_data_sz = transfer.rx_data_sz =
240 		                          st->st_data.iov_len);
241 	if (error == 0)
242 		error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
243 	if (error == 0) {
244 		error = copyout(transfer.rx_cmd, st->st_command.iov_base,
245 		    transfer.rx_cmd_sz);
246 		if ((error == 0) && (st->st_data.iov_len > 0))
247 			error = copyout(transfer.rx_data, st->st_data.iov_base,
248 			    transfer.rx_data_sz);
249 	}
250 
251 	free(transfer.tx_cmd, M_DEVBUF);
252 	free(transfer.tx_data, M_DEVBUF);
253 	return (error);
254 }
255 
256 static int
257 spigen_transfer_mmapped(struct cdev *cdev, struct spigen_transfer_mmapped *stm)
258 {
259 	struct spi_command transfer = SPI_COMMAND_INITIALIZER;
260 	device_t dev = cdev->si_drv1;
261 	struct spigen_softc *sc = device_get_softc(dev);
262 	int error = 0;
263 
264 	mtx_lock(&sc->sc_mtx);
265 	if (sc->sc_mmap_busy)
266 		error = EBUSY;
267 	else if (stm->stm_command_length > sc->sc_command_length_max ||
268 	    stm->stm_data_length > sc->sc_data_length_max)
269 		error = E2BIG;
270 	else if (sc->sc_mmap_buffer == NULL)
271 		error = EINVAL;
272 	else if (sc->sc_mmap_buffer_size <
273 	    stm->stm_command_length + stm->stm_data_length)
274 		error = ENOMEM;
275 	if (error == 0)
276 		sc->sc_mmap_busy = 1;
277 	mtx_unlock(&sc->sc_mtx);
278 	if (error)
279 		return (error);
280 
281 	transfer.tx_cmd = transfer.rx_cmd = (void *)sc->sc_mmap_kvaddr;
282 	transfer.tx_cmd_sz = transfer.rx_cmd_sz = stm->stm_command_length;
283 	transfer.tx_data = transfer.rx_data =
284 	    (void *)(sc->sc_mmap_kvaddr + stm->stm_command_length);
285 	transfer.tx_data_sz = transfer.rx_data_sz = stm->stm_data_length;
286 	error = SPIBUS_TRANSFER(device_get_parent(dev), dev, &transfer);
287 
288 	mtx_lock(&sc->sc_mtx);
289 	KASSERT(sc->sc_mmap_busy, ("mmap no longer marked busy"));
290 	sc->sc_mmap_busy = 0;
291 	mtx_unlock(&sc->sc_mtx);
292 	return (error);
293 }
294 
295 static int
296 spigen_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
297     struct thread *td)
298 {
299 	device_t dev = cdev->si_drv1;
300 	struct spigen_softc *sc = device_get_softc(dev);
301 	int error;
302 
303 	switch (cmd) {
304 	case SPIGENIOC_TRANSFER:
305 		error = spigen_transfer(cdev, (struct spigen_transfer *)data);
306 		break;
307 	case SPIGENIOC_TRANSFER_MMAPPED:
308 		error = spigen_transfer_mmapped(cdev, (struct spigen_transfer_mmapped *)data);
309 		break;
310 	case SPIGENIOC_GET_CLOCK_SPEED:
311 		mtx_lock(&sc->sc_mtx);
312 		*(uint32_t *)data = sc->sc_clock_speed;
313 		/* XXX TODO: implement spibus ivar call */
314 		mtx_unlock(&sc->sc_mtx);
315 		error = 0;
316 		break;
317 	case SPIGENIOC_SET_CLOCK_SPEED:
318 		mtx_lock(&sc->sc_mtx);
319 		sc->sc_clock_speed = *(uint32_t *)data;
320 		mtx_unlock(&sc->sc_mtx);
321 		error = 0;
322 		break;
323 	default:
324 		error = EOPNOTSUPP;
325 	}
326 	return (error);
327 }
328 
329 static int
330 spigen_mmap_single(struct cdev *cdev, vm_ooffset_t *offset,
331     vm_size_t size, struct vm_object **object, int nprot)
332 {
333 	device_t dev = cdev->si_drv1;
334 	struct spigen_softc *sc = device_get_softc(dev);
335 	vm_page_t *m;
336 	size_t n, pages;
337 
338 	if (size == 0 ||
339 	    (nprot & (PROT_EXEC | PROT_READ | PROT_WRITE))
340 	    != (PROT_READ | PROT_WRITE))
341 		return (EINVAL);
342 	size = roundup2(size, PAGE_SIZE);
343 	pages = size / PAGE_SIZE;
344 
345 	mtx_lock(&sc->sc_mtx);
346 	if (sc->sc_mmap_buffer != NULL) {
347 		mtx_unlock(&sc->sc_mtx);
348 		return (EBUSY);
349 	} else if (size > sc->sc_command_length_max + sc->sc_data_length_max) {
350 		mtx_unlock(&sc->sc_mtx);
351 		return (E2BIG);
352 	}
353 	sc->sc_mmap_buffer_size = size;
354 	*offset = 0;
355 	sc->sc_mmap_buffer = *object = vm_pager_allocate(OBJT_PHYS, 0, size,
356 	    nprot, *offset, curthread->td_ucred);
357 	m = malloc(sizeof(*m) * pages, M_TEMP, M_WAITOK);
358 	VM_OBJECT_WLOCK(*object);
359 	vm_object_reference_locked(*object); // kernel and userland both
360 	for (n = 0; n < pages; n++) {
361 		m[n] = vm_page_grab(*object, n,
362 		    VM_ALLOC_NOBUSY | VM_ALLOC_ZERO | VM_ALLOC_WIRED);
363 		m[n]->valid = VM_PAGE_BITS_ALL;
364 	}
365 	VM_OBJECT_WUNLOCK(*object);
366 	sc->sc_mmap_kvaddr = kva_alloc(size);
367 	pmap_qenter(sc->sc_mmap_kvaddr, m, pages);
368 	free(m, M_TEMP);
369 	mtx_unlock(&sc->sc_mtx);
370 
371 	if (*object == NULL)
372 		 return (EINVAL);
373 	return (0);
374 }
375 
376 static int
377 spigen_close(struct cdev *cdev, int fflag, int devtype, struct thread *td)
378 {
379 	device_t dev = cdev->si_drv1;
380 	struct spigen_softc *sc = device_get_softc(dev);
381 
382 	mtx_lock(&sc->sc_mtx);
383 	if (sc->sc_mmap_buffer != NULL) {
384 		pmap_qremove(sc->sc_mmap_kvaddr,
385 		    sc->sc_mmap_buffer_size / PAGE_SIZE);
386 		kva_free(sc->sc_mmap_kvaddr, sc->sc_mmap_buffer_size);
387 		sc->sc_mmap_kvaddr = 0;
388 		vm_object_deallocate(sc->sc_mmap_buffer);
389 		sc->sc_mmap_buffer = NULL;
390 		sc->sc_mmap_buffer_size = 0;
391 	}
392 	mtx_unlock(&sc->sc_mtx);
393 	return (0);
394 }
395 
396 static int
397 spigen_detach(device_t dev)
398 {
399 
400 	return (EIO);
401 }
402 
403 static devclass_t spigen_devclass;
404 
405 static device_method_t spigen_methods[] = {
406 	/* Device interface */
407 #ifdef FDT
408 	DEVMETHOD(device_identify,	spigen_identify),
409 #endif
410 	DEVMETHOD(device_probe,		spigen_probe),
411 	DEVMETHOD(device_attach,	spigen_attach),
412 	DEVMETHOD(device_detach,	spigen_detach),
413 
414 	{ 0, 0 }
415 };
416 
417 static driver_t spigen_driver = {
418 	"spigen",
419 	spigen_methods,
420 	sizeof(struct spigen_softc),
421 };
422 
423 DRIVER_MODULE(spigen, spibus, spigen_driver, spigen_devclass, 0, 0);
424