source: rtems/bsps/shared/grlib/pci/gr_tmtc_1553.c

Last change on this file was 5d5b9ee, checked in by Daniel Cederman <cederman@…>, on 11/14/22 at 09:59:08

bsps/shared/grlib: Change license to BSD-2 for files with Gaisler copyright

This patch changes the license to BSD-2 for all source files where the
copyright is held by Aeroflex Gaisler, Cobham Gaisler, or Gaisler Research.
Some files also includes copyright right statements from OAR and/or
embedded Brains in addition to Gaisler.

Updates #3053.

  • Property mode set to 100644
File size: 16.3 KB
Line 
1/* SPDX-License-Identifier: BSD-2-Clause */
2
3/*  GR-TMTC-1553 PCI Target driver.
4 *
5 *  COPYRIGHT (c) 2008.
6 *  Cobham Gaisler AB.
7 *
8 *  Configures the GR-TMTC-1553 interface PCI board.
9 *  This driver provides a AMBA PnP bus by using the general part
10 *  of the AMBA PnP bus driver (ambapp_bus.c).
11 *
12 *  Driver resources for the AMBA PnP bus provided can be set using
13 *  gr_tmtc_1553_set_resources().
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 *    notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 *    notice, this list of conditions and the following disclaimer in the
22 *    documentation and/or other materials provided with the distribution.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37#include <inttypes.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43
44#include <bsp.h>
45#include <rtems/bspIo.h>
46#include <pci.h>
47#include <pci/access.h>
48
49#include <grlib/ambapp.h>
50#include <grlib/grlib.h>
51#include <drvmgr/drvmgr.h>
52#include <grlib/ambapp_bus.h>
53#include <drvmgr/pci_bus.h>
54#include <grlib/bspcommon.h>
55#include <grlib/genirq.h>
56
57#include <grlib/gr_tmtc_1553.h>
58
59#include <grlib/grlib_impl.h>
60
61/*#define DEBUG 1 */
62
63#ifdef DEBUG
64#define DBG(x...) printk(x)
65#else
66#define DBG(x...)
67#endif
68
69/* PCI ID */
70#define PCIID_VENDOR_GAISLER            0x1AC8
71
72int gr_tmtc_1553_init1(struct drvmgr_dev *dev);
73int gr_tmtc_1553_init2(struct drvmgr_dev *dev);
74void gr_tmtc_1553_isr (void *arg);
75
76struct gr_tmtc_1553_ver {
77        const unsigned int      amba_freq_hz;   /* The frequency */
78        const unsigned int      amba_ioarea;    /* The address where the PnP IOAREA starts at */
79};
80
81/* Private data structure for driver */
82struct gr_tmtc_1553_priv {
83        /* Driver management */
84        struct drvmgr_dev               *dev;
85        char                            prefix[32];
86        SPIN_DECLARE(devlock);
87
88        /* PCI */
89        pci_dev_t                       pcidev;
90        struct pci_dev_info             *devinfo;
91
92        /* IRQ */
93        genirq_t                        genirq;
94
95        struct gr_tmtc_1553_ver         *version;
96        struct irqmp_regs               *irq;
97        struct drvmgr_map_entry         bus_maps_down[2];
98
99        struct ambapp_bus               abus;
100        struct ambapp_mmap              amba_maps[4];
101        struct ambapp_config            config;
102};
103
104struct gr_tmtc_1553_ver gr_tmtc_1553_ver0 = {
105        .amba_freq_hz           = 33333333,
106        .amba_ioarea            = 0xfff00000,
107};
108
109
110int ambapp_tmtc_1553_int_register(
111        struct drvmgr_dev *dev,
112        int irq,
113        const char *info,
114        drvmgr_isr handler,
115        void *arg);
116int ambapp_tmtc_1553_int_unregister(
117        struct drvmgr_dev *dev,
118        int irq,
119        drvmgr_isr handler,
120        void *arg);
121int ambapp_tmtc_1553_int_unmask(
122        struct drvmgr_dev *dev,
123        int irq);
124int ambapp_tmtc_1553_int_mask(
125        struct drvmgr_dev *dev,
126        int irq);
127int ambapp_tmtc_1553_int_clear(
128        struct drvmgr_dev *dev,
129        int irq);
130int ambapp_tmtc_1553_get_params(
131        struct drvmgr_dev *dev,
132        struct drvmgr_bus_params *params);
133
134struct ambapp_ops ambapp_tmtc_1553_ops = {
135        .int_register = ambapp_tmtc_1553_int_register,
136        .int_unregister = ambapp_tmtc_1553_int_unregister,
137        .int_unmask = ambapp_tmtc_1553_int_unmask,
138        .int_mask = ambapp_tmtc_1553_int_mask,
139        .int_clear = ambapp_tmtc_1553_int_clear,
140        .get_params = ambapp_tmtc_1553_get_params
141};
142
143struct drvmgr_drv_ops gr_tmtc_1553_ops =
144{
145        {gr_tmtc_1553_init1, gr_tmtc_1553_init2, NULL, NULL},
146        NULL,
147        NULL
148};
149
150struct pci_dev_id_match gr_tmtc_1553_ids[] =
151{
152        PCIID_DEVVEND(PCIID_VENDOR_GAISLER, PCIID_DEVICE_GR_TMTC_1553),
153        PCIID_END_TABLE /* Mark end of table */
154};
155
156struct pci_drv_info gr_tmtc_1553_info =
157{
158        {
159                DRVMGR_OBJ_DRV,                 /* Driver */
160                NULL,                           /* Next driver */
161                NULL,                           /* Device list */
162                DRIVER_PCI_GAISLER_TMTC_1553_ID, /* Driver ID */
163                "GR-TMTC-1553_DRV",             /* Driver Name */
164                DRVMGR_BUS_TYPE_PCI,            /* Bus Type */
165                &gr_tmtc_1553_ops,
166                NULL,                           /* Funcs */
167                0,                              /* No devices yet */
168                0,
169        },
170        &gr_tmtc_1553_ids[0]
171};
172
173/* Driver resources configuration for the AMBA bus on the GR-RASTA-IO board.
174 * It is declared weak so that the user may override it from the project file,
175 * if the default settings are not enough.
176 *
177 * The configuration consists of an array of configuration pointers, each
178 * pointer determine the configuration of one GR-RASTA-IO board. Pointer
179 * zero is for board0, pointer 1 for board1 and so on.
180 *
181 * The array must end with a NULL pointer.
182 */
183struct drvmgr_bus_res *gr_tmtc_1553_resources[] __attribute__((weak)) =
184{
185        NULL
186};
187
188void gr_tmtc_1553_register_drv(void)
189{
190        DBG("Registering GR-TMTC-1553 PCI driver\n");
191        drvmgr_drv_register(&gr_tmtc_1553_info.general);
192}
193
194void gr_tmtc_1553_isr (void *arg)
195{
196        struct gr_tmtc_1553_priv *priv = arg;
197        unsigned int status, tmp;
198        int irq;
199        SPIN_ISR_IRQFLAGS(irqflags);
200
201        tmp = status = priv->irq->ipend;
202
203        /* DBG("GR-RASTA-IO: IRQ 0x%x\n",status); */
204
205        SPIN_LOCK(&priv->devlock, irqflags);
206        for(irq=0; irq<16; irq++) {
207                if ( status & (1<<irq) ) {
208                        genirq_doirq(priv->genirq, irq);
209                        priv->irq->iclear = (1<<irq);
210                        status &= ~(1<<irq);
211                        if ( status == 0 )
212                                break;
213                }
214        }
215        SPIN_UNLOCK(&priv->devlock, irqflags);
216
217        /* ACK interrupt, this is because PCI is Level, so the IRQ Controller still drives the IRQ. */
218        if ( tmp )
219                drvmgr_interrupt_clear(priv->dev, 0);
220
221        DBG("GR-TMTC-1553-IRQ: 0x%x\n", tmp);
222}
223
224static int gr_tmtc_1553_hw_init(struct gr_tmtc_1553_priv *priv)
225{
226        unsigned int *page0 = NULL;
227        struct ambapp_dev *tmp;
228        unsigned int pci_freq_hz;
229        struct pci_dev_info *devinfo = priv->devinfo;
230        uint32_t bar0, bar0_size;
231
232        /* Select version of GR-TMTC-1553 board */
233        switch (devinfo->rev) {
234                case 0:
235                        priv->version = &gr_tmtc_1553_ver0;
236                        break;
237                default:
238                        return -2;
239        }
240
241        bar0 = devinfo->resources[0].address;
242        bar0_size = devinfo->resources[0].size;
243        page0 = (unsigned int *)(bar0 + bar0_size/2);
244
245        /* Point PAGE0 to start of board address map. RAM at 0xff000000, APB at 0xffc00000, IOAREA at 0xfff000000 */
246        /* XXX We assume little endian host with byte twisting enabled here */
247        *page0 = 0x010000ff;    /* Set little endian mode on peripheral. */
248
249        /* Scan AMBA Plug&Play */
250
251        /* AMBA MAP bar0 (in CPU) ==> 0x80000000(remote amba address) */
252        priv->amba_maps[0].size = 0x1000000;
253        priv->amba_maps[0].local_adr = bar0;
254        priv->amba_maps[0].remote_adr = 0xff000000;
255       
256        /* Addresses not matching with map be untouched */
257        priv->amba_maps[2].size = 0xfffffff0;
258        priv->amba_maps[2].local_adr = 0;
259        priv->amba_maps[2].remote_adr = 0;
260
261        /* Mark end of table */
262        priv->amba_maps[3].size=0;
263        priv->amba_maps[3].local_adr = 0;
264        priv->amba_maps[3].remote_adr = 0;
265
266        /* Start AMBA PnP scan at first AHB bus */
267        ambapp_scan(&priv->abus,
268                bar0 + (priv->version->amba_ioarea & ~0xff000000),
269                NULL, &priv->amba_maps[0]);
270
271        /* Frequency is the hsame as the PCI bus frequency */
272        drvmgr_freq_get(priv->dev, 0, &pci_freq_hz);
273
274        ambapp_freq_init(&priv->abus, NULL, pci_freq_hz);
275
276        /* Find IRQ controller */
277        tmp = (struct ambapp_dev *)ambapp_for_each(&priv->abus,
278                                        (OPTIONS_ALL|OPTIONS_APB_SLVS),
279                                        VENDOR_GAISLER, GAISLER_IRQMP,
280                                        ambapp_find_by_idx, NULL);
281        if ( !tmp ) {
282                return -4;
283        }
284        priv->irq = (struct irqmp_regs *)DEV_TO_APB(tmp)->start;
285        /* Set up irq controller */
286        priv->irq->mask[0] = 0;
287        priv->irq->iclear = 0xffff;
288        priv->irq->ilevel = 0;
289
290        /* DOWN streams translation table */
291        priv->bus_maps_down[0].name = "PCI BAR0 -> AMBA";
292        priv->bus_maps_down[0].size = priv->amba_maps[0].size;
293        priv->bus_maps_down[0].from_adr = (void *)priv->amba_maps[0].local_adr;
294        priv->bus_maps_down[0].to_adr = (void *)priv->amba_maps[0].remote_adr;
295        /* Mark end of translation table */
296        priv->bus_maps_down[1].size = 0;
297
298        /* Successfully registered the board */
299        return 0;
300}
301
302
303/* Called when a PCI target is found with the PCI device and vendor ID
304 * given in gr_tmtc_1553_ids[].
305 */
306int gr_tmtc_1553_init1(struct drvmgr_dev *dev)
307{
308        struct gr_tmtc_1553_priv *priv;
309        struct pci_dev_info *devinfo;
310        int status;
311        uint32_t bar0, bar0_size;
312        int resources_cnt;
313        int sc;
314
315        /* PCI device does not have the IRQ line register, when PCI autoconf configures it the configuration
316         * is forgotten. We take the IRQ number from the PCI Host device (AMBA device), this works as long
317         * as PCI-IRQs are ored together on the bus.
318         *
319         * Note that this only works on LEON.
320         */
321        ((struct pci_dev_info *)dev->businfo)->irq = ((struct amba_dev_info *)dev->parent->dev->businfo)->info.irq;
322
323        priv = grlib_calloc(1, sizeof(*priv));
324        if ( !priv )
325                return DRVMGR_NOMEM;
326
327        dev->priv = priv;
328        priv->dev = dev;
329
330        /* Determine number of configurations */
331        resources_cnt = get_resarray_count(gr_tmtc_1553_resources);
332
333        /* Generate Device prefix */
334
335        strcpy(priv->prefix, "/dev/tmtc1553_0");
336        priv->prefix[14] += dev->minor_drv;
337        sc = mkdir(priv->prefix, S_IRWXU | S_IRWXG | S_IRWXO);
338        _Assert_Unused_variable_equals(sc, 0);
339        priv->prefix[15] = '/';
340        priv->prefix[16] = '\0';
341
342        priv->devinfo = devinfo = (struct pci_dev_info *)dev->businfo;
343        priv->pcidev = devinfo->pcidev;
344        bar0 = devinfo->resources[0].address;
345        bar0_size = devinfo->resources[0].size;
346        printk("\n\n--- GR-TMTC-1553[%d] ---\n", dev->minor_drv);
347        printk(" PCI BUS: 0x%x, SLOT: 0x%x, FUNCTION: 0x%x\n",
348                PCI_DEV_EXPAND(priv->pcidev));
349        printk(" PCI VENDOR: 0x%04x, DEVICE: 0x%04x\n",
350                devinfo->id.vendor, devinfo->id.device);
351        printk(" PCI BAR[0]: 0x%" PRIx32 " - 0x%" PRIx32 "\n",
352                bar0, bar0 + bar0_size - 1);
353        printk(" IRQ: %d\n\n\n", devinfo->irq);
354
355        /* all neccessary space assigned to GR-TMTC-1553 target? */
356        if (bar0_size == 0)
357                return DRVMGR_ENORES;
358
359        /* Initialize spin-lock for this PCI peripheral device. This is to
360         * protect the Interrupt Controller Registers. The genirq layer is
361         * protecting its own internals and ISR dispatching.
362         */
363        SPIN_INIT(&priv->devlock, priv->prefix);
364
365        priv->genirq = genirq_init(16);
366        if ( priv->genirq == NULL ) {
367                free(priv);
368                dev->priv = NULL;
369                return DRVMGR_FAIL;
370        }
371
372        status = gr_tmtc_1553_hw_init(priv);
373        if ( status != 0 ) {
374                genirq_destroy(priv->genirq);
375                free(priv);
376                dev->priv = NULL;
377                printk(" Failed to initialize GR-TMTC-1553 HW: %d\n", status);
378                return DRVMGR_FAIL;
379        }
380
381        /* Init amba bus */
382        priv->config.abus = &priv->abus;
383        priv->config.ops = &ambapp_tmtc_1553_ops;
384        priv->config.maps_down = &priv->bus_maps_down[0];
385        /* This PCI device has only target interface so DMA is not supported,
386         * which means that translation from AMBA->PCI should fail if attempted.
387         */
388        priv->config.maps_up = DRVMGR_TRANSLATE_NO_BRIDGE;
389        if ( priv->dev->minor_drv < resources_cnt ) {
390                priv->config.resources = gr_tmtc_1553_resources[priv->dev->minor_drv];
391        } else {
392                priv->config.resources = NULL;
393        }
394
395        /* Create And Register AMBA PnP Bus */
396        return ambapp_bus_register(dev, &priv->config);
397}
398
399int gr_tmtc_1553_init2(struct drvmgr_dev *dev)
400{
401        struct gr_tmtc_1553_priv *priv = dev->priv;
402
403        /* Clear any old interrupt requests */
404        drvmgr_interrupt_clear(dev, 0);
405
406        /* Enable System IRQ so that GR-TMTC-1553 PCI target interrupt goes through.
407         *
408         * It is important to enable it in stage init2. If interrupts were enabled in init1
409         * this might hang the system when more than one PCI target is connected, this is
410         * because PCI interrupts might be shared and PCI target 2 have not initialized and
411         * might therefore drive interrupt already when entering init1().
412         */
413        drvmgr_interrupt_register(
414                dev,
415                0,
416                "gr_tmtc_1553",
417                gr_tmtc_1553_isr,
418                (void *)priv);
419
420        return DRVMGR_OK;
421}
422
423int ambapp_tmtc_1553_int_register(
424        struct drvmgr_dev *dev,
425        int irq,
426        const char *info,
427        drvmgr_isr handler,
428        void *arg)
429{
430        struct gr_tmtc_1553_priv *priv = dev->parent->dev->priv;
431        SPIN_IRQFLAGS(irqflags);
432        int status;
433        void *h;
434
435        h = genirq_alloc_handler(handler, arg);
436        if ( h == NULL )
437                return DRVMGR_FAIL;
438
439        SPIN_LOCK_IRQ(&priv->devlock, irqflags);
440
441        status = genirq_register(priv->genirq, irq, h);
442        if ( status == 0 ) {
443                /* Disable and clear IRQ for first registered handler */
444                priv->irq->iclear = (1<<irq);
445                priv->irq->mask[0] &= ~(1<<irq); /* mask interrupt source */
446        } else if ( status == 1 )
447                status = 0;
448
449        if (status != 0) {
450                SPIN_UNLOCK_IRQ(&priv->devlock, irqflags);
451                genirq_free_handler(h);
452                return DRVMGR_FAIL;
453        }
454
455        status = genirq_enable(priv->genirq, irq, handler, arg);
456        if ( status == 0 ) {
457                /* Enable IRQ for first enabled handler only */
458                priv->irq->mask[0] |= (1<<irq); /* unmask interrupt source */
459        } else if ( status == 1 )
460                status = 0;
461
462        SPIN_UNLOCK_IRQ(&priv->devlock, irqflags);
463
464        return status;
465}
466
467int ambapp_tmtc_1553_int_unregister(
468        struct drvmgr_dev *dev,
469        int irq,
470        drvmgr_isr isr,
471        void *arg)
472{
473        struct gr_tmtc_1553_priv *priv = dev->parent->dev->priv;
474        SPIN_IRQFLAGS(irqflags);
475        int status;
476        void *handler;
477
478        SPIN_LOCK_IRQ(&priv->devlock, irqflags);
479
480        status = genirq_disable(priv->genirq, irq, isr, arg);
481        if ( status == 0 ) {
482                /* Disable IRQ only when no enabled handler exists */
483                priv->irq->mask[0] &= ~(1<<irq); /* mask interrupt source */
484        } else if ( status == 1 )
485                status = 0;
486
487        handler = genirq_unregister(priv->genirq, irq, isr, arg);
488        if ( handler == NULL )
489                status = DRVMGR_FAIL;
490        else
491                status = DRVMGR_OK;
492
493        SPIN_UNLOCK_IRQ(&priv->devlock, irqflags);
494
495        if (handler)
496                genirq_free_handler(handler);
497
498        return status;
499}
500
501int ambapp_tmtc_1553_int_unmask(
502        struct drvmgr_dev *dev,
503        int irq)
504{
505        struct gr_tmtc_1553_priv *priv = dev->parent->dev->priv;
506        SPIN_IRQFLAGS(irqflags);
507
508        DBG("TMTC-1553 IRQ %d: enable\n", irq);
509       
510        if ( genirq_check(priv->genirq, irq) )
511                return DRVMGR_FAIL;
512
513        SPIN_LOCK_IRQ(&priv->devlock, irqflags);
514
515        /* Enable IRQ */
516        priv->irq->mask[0] |= (1<<irq); /* unmask interrupt source */
517
518        SPIN_UNLOCK_IRQ(&priv->devlock, irqflags);
519
520        return DRVMGR_OK;
521}
522
523int ambapp_tmtc_1553_int_mask(
524        struct drvmgr_dev *dev,
525        int irq)
526{
527        struct gr_tmtc_1553_priv *priv = dev->parent->dev->priv;
528        SPIN_IRQFLAGS(irqflags);
529
530        DBG("TMTC-1553 IRQ %d: disable\n", irq);
531
532        if ( genirq_check(priv->genirq, irq) )
533                return DRVMGR_FAIL;
534
535        SPIN_LOCK_IRQ(&priv->devlock, irqflags);
536
537        /* Disable IRQ */
538        priv->irq->mask[0] &= ~(1<<irq); /* mask interrupt source */
539
540        SPIN_UNLOCK_IRQ(&priv->devlock, irqflags);
541
542        return DRVMGR_OK;
543}
544
545int ambapp_tmtc_1553_int_clear(
546        struct drvmgr_dev *dev,
547        int irq)
548{
549        struct gr_tmtc_1553_priv *priv = dev->parent->dev->priv;
550
551        if ( genirq_check(priv->genirq, irq) )
552                return DRVMGR_FAIL;
553
554        priv->irq->iclear = (1<<irq);
555
556        return DRVMGR_OK;
557}
558
559int ambapp_tmtc_1553_get_params(struct drvmgr_dev *dev, struct drvmgr_bus_params *params)
560{
561        struct gr_tmtc_1553_priv *priv = dev->parent->dev->priv;
562
563        /* Device name prefix pointer, skip /dev */
564        params->dev_prefix = &priv->prefix[5];
565
566        return 0;
567}
568
569void gr_tmtc_1553_print_dev(struct drvmgr_dev *dev, int options)
570{
571        struct gr_tmtc_1553_priv *priv = dev->priv;
572        struct pci_dev_info *devinfo = priv->devinfo;
573        uint32_t bar0, bar0_size;
574
575        /* Print */
576        printf("--- GR-TMTC-1553 [bus 0x%x, dev 0x%x, fun 0x%x] ---\n",
577                PCI_DEV_EXPAND(priv->pcidev));
578
579        bar0 = devinfo->resources[0].address;
580        bar0_size = devinfo->resources[0].size;
581
582        printf(" PCI BAR[0]: 0x%" PRIx32 " - 0x%" PRIx32 "\n",
583                bar0, bar0 + bar0_size - 1);
584        printf(" IRQ REGS:        0x%" PRIxPTR "\n", (uintptr_t)priv->irq);
585        printf(" IRQ:             %d\n", devinfo->irq);
586        printf(" FREQ:            %d Hz\n", priv->version->amba_freq_hz);
587        printf(" IMASK:           0x%08x\n", priv->irq->mask[0]);
588        printf(" IPEND:           0x%08x\n", priv->irq->ipend);
589
590        /* Print amba config */
591        if ( options & TMTC_1553_OPTIONS_AMBA ) {
592                ambapp_print(&priv->abus, 10);
593        }
594#if 0
595        /* Print IRQ handlers and their arguments */
596        if ( options & TMTC_1553_OPTIONS_IRQ ) {
597                int i;
598                for(i=0; i<16; i++) {
599                        printf(" IRQ[%02d]:         0x%x, arg: 0x%x\n",
600                                i, (unsigned int)priv->isrs[i].handler, (unsigned int)priv->isrs[i].arg);
601                }
602        }
603#endif
604}
605
606void gr_tmtc_1553_print(int options)
607{
608        struct pci_drv_info *drv = &gr_tmtc_1553_info;
609        struct drvmgr_dev *dev;
610
611        dev = drv->general.dev;
612        while(dev) {
613                gr_tmtc_1553_print_dev(dev, options);
614                dev = dev->next_in_drv;
615        }
616}
Note: See TracBrowser for help on using the repository browser.