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