]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* - |
2 | * BSD LICENSE | |
3 | * | |
4 | * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. | |
5 | * All rights reserved. | |
6 | * | |
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions | |
9 | * are met: | |
10 | * | |
11 | * * Redistributions of source code must retain the above copyright | |
12 | * notice, this list of conditions and the following disclaimer. | |
13 | * * Redistributions in binary form must reproduce the above copyright | |
14 | * notice, this list of conditions and the following disclaimer in | |
15 | * the documentation and/or other materials provided with the | |
16 | * distribution. | |
17 | * * Neither the name of Intel Corporation nor the names of its | |
18 | * contributors may be used to endorse or promote products derived | |
19 | * from this software without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
22 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
23 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
25 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
26 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
27 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
28 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
29 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
31 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
32 | */ | |
33 | #include <sys/cdefs.h> | |
34 | __FBSDID("$FreeBSD$"); | |
35 | ||
36 | #include <sys/param.h> /* defines used in kernel.h */ | |
37 | #include <sys/module.h> | |
38 | #include <sys/kernel.h> /* types used in module initialization */ | |
39 | #include <sys/conf.h> /* cdevsw struct */ | |
40 | #include <sys/bus.h> /* structs, prototypes for pci bus stuff and DEVMETHOD */ | |
41 | #include <sys/rman.h> | |
42 | #include <sys/systm.h> | |
43 | #include <sys/rwlock.h> | |
44 | #include <sys/proc.h> | |
45 | ||
46 | #include <machine/bus.h> | |
47 | #include <dev/pci/pcivar.h> /* For pci_get macros! */ | |
48 | #include <dev/pci/pcireg.h> /* The softc holds our per-instance data. */ | |
49 | #include <vm/vm.h> | |
50 | #include <vm/uma.h> | |
51 | #include <vm/vm_object.h> | |
52 | #include <vm/vm_page.h> | |
53 | #include <vm/vm_pager.h> | |
54 | ||
55 | ||
56 | #define MAX_BARS (PCIR_MAX_BAR_0 + 1) | |
57 | ||
58 | #define MAX_DETACHED_DEVICES 128 | |
59 | static device_t detached_devices[MAX_DETACHED_DEVICES] = {}; | |
60 | static int num_detached = 0; | |
61 | ||
62 | struct nic_uio_softc { | |
63 | device_t dev_t; | |
64 | struct cdev *my_cdev; | |
65 | int bar_id[MAX_BARS]; | |
66 | struct resource *bar_res[MAX_BARS]; | |
67 | u_long bar_start[MAX_BARS]; | |
68 | u_long bar_size[MAX_BARS]; | |
69 | }; | |
70 | ||
71 | /* Function prototypes */ | |
72 | static d_open_t nic_uio_open; | |
73 | static d_close_t nic_uio_close; | |
74 | static d_mmap_t nic_uio_mmap; | |
75 | static d_mmap_single_t nic_uio_mmap_single; | |
76 | static int nic_uio_probe(device_t dev); | |
77 | static int nic_uio_attach(device_t dev); | |
78 | static int nic_uio_detach(device_t dev); | |
79 | static int nic_uio_shutdown(void); | |
80 | static int nic_uio_modevent(module_t mod, int type, void *arg); | |
81 | ||
82 | static struct cdevsw uio_cdevsw = { | |
83 | .d_name = "nic_uio", | |
84 | .d_version = D_VERSION, | |
85 | .d_open = nic_uio_open, | |
86 | .d_close = nic_uio_close, | |
87 | .d_mmap = nic_uio_mmap, | |
88 | .d_mmap_single = nic_uio_mmap_single, | |
89 | }; | |
90 | ||
91 | static device_method_t nic_uio_methods[] = { | |
92 | DEVMETHOD(device_probe, nic_uio_probe), | |
93 | DEVMETHOD(device_attach, nic_uio_attach), | |
94 | DEVMETHOD(device_detach, nic_uio_detach), | |
95 | DEVMETHOD_END | |
96 | }; | |
97 | ||
98 | struct device { | |
99 | int vend; | |
100 | int dev; | |
101 | }; | |
102 | ||
103 | struct pci_bdf { | |
104 | uint32_t bus; | |
105 | uint32_t devid; | |
106 | uint32_t function; | |
107 | }; | |
108 | ||
109 | static devclass_t nic_uio_devclass; | |
110 | ||
111 | DEFINE_CLASS_0(nic_uio, nic_uio_driver, nic_uio_methods, sizeof(struct nic_uio_softc)); | |
112 | DRIVER_MODULE(nic_uio, pci, nic_uio_driver, nic_uio_devclass, nic_uio_modevent, 0); | |
113 | ||
114 | static int | |
115 | nic_uio_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr, | |
116 | int prot, vm_memattr_t *memattr) | |
117 | { | |
118 | *paddr = offset; | |
119 | return 0; | |
120 | } | |
121 | ||
122 | static int | |
123 | nic_uio_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t size, | |
124 | struct vm_object **obj, int nprot) | |
125 | { | |
126 | /* | |
127 | * The BAR index is encoded in the offset. Divide the offset by | |
128 | * PAGE_SIZE to get the index of the bar requested by the user | |
129 | * app. | |
130 | */ | |
131 | unsigned bar = *offset/PAGE_SIZE; | |
132 | struct nic_uio_softc *sc = cdev->si_drv1; | |
133 | ||
134 | if (bar >= MAX_BARS) | |
135 | return EINVAL; | |
136 | ||
137 | if (sc->bar_res[bar] == NULL) { | |
138 | sc->bar_id[bar] = PCIR_BAR(bar); | |
139 | ||
140 | if (PCI_BAR_IO(pci_read_config(sc->dev_t, sc->bar_id[bar], 4))) | |
141 | sc->bar_res[bar] = bus_alloc_resource_any(sc->dev_t, SYS_RES_IOPORT, | |
142 | &sc->bar_id[bar], RF_ACTIVE); | |
143 | else | |
144 | sc->bar_res[bar] = bus_alloc_resource_any(sc->dev_t, SYS_RES_MEMORY, | |
145 | &sc->bar_id[bar], RF_ACTIVE); | |
146 | } | |
147 | if (sc->bar_res[bar] == NULL) | |
148 | return ENXIO; | |
149 | ||
150 | sc->bar_start[bar] = rman_get_start(sc->bar_res[bar]); | |
151 | sc->bar_size[bar] = rman_get_size(sc->bar_res[bar]); | |
152 | ||
153 | device_printf(sc->dev_t, "Bar %u @ %lx, size %lx\n", bar, | |
154 | sc->bar_start[bar], sc->bar_size[bar]); | |
155 | ||
156 | *offset = sc->bar_start[bar]; | |
157 | *obj = vm_pager_allocate(OBJT_DEVICE, cdev, size, nprot, *offset, | |
158 | curthread->td_ucred); | |
159 | return 0; | |
160 | } | |
161 | ||
162 | ||
163 | int | |
164 | nic_uio_open(struct cdev *dev, int oflags, int devtype, struct thread *td) | |
165 | { | |
166 | return 0; | |
167 | } | |
168 | ||
169 | int | |
170 | nic_uio_close(struct cdev *dev, int fflag, int devtype, struct thread *td) | |
171 | { | |
172 | return 0; | |
173 | } | |
174 | ||
175 | static int | |
176 | nic_uio_probe (device_t dev) | |
177 | { | |
178 | int i; | |
179 | unsigned int bus = pci_get_bus(dev); | |
180 | unsigned int device = pci_get_slot(dev); | |
181 | unsigned int function = pci_get_function(dev); | |
182 | ||
11fdf7f2 TL |
183 | char bdf_str[256]; |
184 | char *token, *remaining; | |
185 | ||
186 | /* First check if we found this on load */ | |
7c673cae FG |
187 | for (i = 0; i < num_detached; i++) |
188 | if (bus == pci_get_bus(detached_devices[i]) && | |
189 | device == pci_get_slot(detached_devices[i]) && | |
190 | function == pci_get_function(detached_devices[i])) { | |
191 | device_set_desc(dev, "DPDK PCI Device"); | |
192 | return BUS_PROBE_SPECIFIC; | |
193 | } | |
194 | ||
11fdf7f2 TL |
195 | /* otherwise check if it's a new device and if it matches the BDF */ |
196 | memset(bdf_str, 0, sizeof(bdf_str)); | |
197 | TUNABLE_STR_FETCH("hw.nic_uio.bdfs", bdf_str, sizeof(bdf_str)); | |
198 | remaining = bdf_str; | |
199 | while (1) { | |
200 | if (remaining == NULL || remaining[0] == '\0') | |
201 | break; | |
202 | token = strsep(&remaining, ",:"); | |
203 | if (token == NULL) | |
204 | break; | |
205 | bus = strtol(token, NULL, 10); | |
206 | token = strsep(&remaining, ",:"); | |
207 | if (token == NULL) | |
208 | break; | |
209 | device = strtol(token, NULL, 10); | |
210 | token = strsep(&remaining, ",:"); | |
211 | if (token == NULL) | |
212 | break; | |
213 | function = strtol(token, NULL, 10); | |
214 | ||
215 | if (bus == pci_get_bus(dev) && | |
216 | device == pci_get_slot(dev) && | |
217 | function == pci_get_function(dev)) { | |
218 | ||
219 | if (num_detached < MAX_DETACHED_DEVICES) { | |
220 | printf("%s: probed dev=%p\n", | |
221 | __func__, dev); | |
222 | detached_devices[num_detached++] = dev; | |
223 | device_set_desc(dev, "DPDK PCI Device"); | |
224 | return BUS_PROBE_SPECIFIC; | |
225 | } else { | |
226 | printf("%s: reached MAX_DETACHED_DEVICES=%d. dev=%p won't be reattached\n", | |
227 | __func__, MAX_DETACHED_DEVICES, | |
228 | dev); | |
229 | break; | |
230 | } | |
231 | } | |
232 | } | |
233 | ||
7c673cae FG |
234 | return ENXIO; |
235 | } | |
236 | ||
237 | static int | |
238 | nic_uio_attach(device_t dev) | |
239 | { | |
240 | int i; | |
241 | struct nic_uio_softc *sc; | |
242 | ||
243 | sc = device_get_softc(dev); | |
244 | sc->dev_t = dev; | |
245 | sc->my_cdev = make_dev(&uio_cdevsw, device_get_unit(dev), | |
246 | UID_ROOT, GID_WHEEL, 0600, "uio@pci:%u:%u:%u", | |
247 | pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev)); | |
248 | if (sc->my_cdev == NULL) | |
249 | return ENXIO; | |
250 | sc->my_cdev->si_drv1 = sc; | |
251 | ||
252 | for (i = 0; i < MAX_BARS; i++) | |
253 | sc->bar_res[i] = NULL; | |
254 | ||
255 | pci_enable_busmaster(dev); | |
256 | ||
257 | return 0; | |
258 | } | |
259 | ||
260 | static int | |
261 | nic_uio_detach(device_t dev) | |
262 | { | |
263 | int i; | |
264 | struct nic_uio_softc *sc; | |
265 | sc = device_get_softc(dev); | |
266 | ||
267 | for (i = 0; i < MAX_BARS; i++) | |
268 | if (sc->bar_res[i] != NULL) { | |
269 | ||
270 | if (PCI_BAR_IO(pci_read_config(dev, sc->bar_id[i], 4))) | |
271 | bus_release_resource(dev, SYS_RES_IOPORT, sc->bar_id[i], | |
272 | sc->bar_res[i]); | |
273 | else | |
274 | bus_release_resource(dev, SYS_RES_MEMORY, sc->bar_id[i], | |
275 | sc->bar_res[i]); | |
276 | } | |
277 | ||
278 | if (sc->my_cdev != NULL) | |
279 | destroy_dev(sc->my_cdev); | |
280 | return 0; | |
281 | } | |
282 | ||
283 | static void | |
284 | nic_uio_load(void) | |
285 | { | |
286 | uint32_t bus, device, function; | |
287 | device_t dev; | |
288 | char bdf_str[256]; | |
289 | char *token, *remaining; | |
290 | ||
291 | memset(bdf_str, 0, sizeof(bdf_str)); | |
292 | TUNABLE_STR_FETCH("hw.nic_uio.bdfs", bdf_str, sizeof(bdf_str)); | |
293 | remaining = bdf_str; | |
11fdf7f2 | 294 | printf("nic_uio: hw.nic_uio.bdfs = '%s'\n", bdf_str); |
7c673cae FG |
295 | /* |
296 | * Users should specify PCI BDFs in the format "b:d:f,b:d:f,b:d:f". | |
297 | * But the code below does not try differentiate between : and , | |
298 | * and just blindly uses 3 tokens at a time to construct a | |
299 | * bus/device/function tuple. | |
300 | * | |
301 | * There is no checking on strtol() return values, but this should | |
302 | * be OK. Worst case is it cannot convert and returns 0. This | |
303 | * could give us a different BDF than intended, but as long as the | |
304 | * PCI device/vendor ID does not match it will not matter. | |
305 | */ | |
306 | while (1) { | |
307 | if (remaining == NULL || remaining[0] == '\0') | |
308 | break; | |
309 | token = strsep(&remaining, ",:"); | |
310 | if (token == NULL) | |
311 | break; | |
312 | bus = strtol(token, NULL, 10); | |
313 | token = strsep(&remaining, ",:"); | |
314 | if (token == NULL) | |
315 | break; | |
316 | device = strtol(token, NULL, 10); | |
317 | token = strsep(&remaining, ",:"); | |
318 | if (token == NULL) | |
319 | break; | |
320 | function = strtol(token, NULL, 10); | |
321 | ||
322 | dev = pci_find_bsf(bus, device, function); | |
323 | if (dev == NULL) | |
324 | continue; | |
325 | ||
326 | if (num_detached < MAX_DETACHED_DEVICES) { | |
327 | printf("nic_uio_load: detaching and storing dev=%p\n", | |
328 | dev); | |
329 | detached_devices[num_detached++] = dev; | |
330 | } else { | |
331 | printf("nic_uio_load: reached MAX_DETACHED_DEVICES=%d. dev=%p won't be reattached\n", | |
332 | MAX_DETACHED_DEVICES, dev); | |
333 | } | |
334 | device_detach(dev); | |
335 | } | |
336 | } | |
337 | ||
338 | static void | |
339 | nic_uio_unload(void) | |
340 | { | |
341 | int i; | |
342 | printf("nic_uio_unload: entered...\n"); | |
343 | ||
344 | for (i = 0; i < num_detached; i++) { | |
345 | printf("nic_uio_unload: calling to device_probe_and_attach for dev=%p...\n", | |
346 | detached_devices[i]); | |
347 | device_probe_and_attach(detached_devices[i]); | |
348 | printf("nic_uio_unload: done.\n"); | |
349 | } | |
350 | ||
351 | printf("nic_uio_unload: leaving...\n"); | |
352 | } | |
353 | ||
354 | static int | |
355 | nic_uio_shutdown(void) | |
356 | { | |
357 | return 0; | |
358 | } | |
359 | ||
360 | static int | |
361 | nic_uio_modevent(module_t mod, int type, void *arg) | |
362 | { | |
363 | ||
364 | switch (type) { | |
365 | case MOD_LOAD: | |
366 | nic_uio_load(); | |
367 | break; | |
368 | case MOD_UNLOAD: | |
369 | nic_uio_unload(); | |
370 | break; | |
371 | case MOD_SHUTDOWN: | |
372 | nic_uio_shutdown(); | |
373 | break; | |
374 | default: | |
375 | break; | |
376 | } | |
377 | ||
378 | return 0; | |
379 | } |