123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684 |
- /*
- * Copyright 2003 Digi International (www.digi.com)
- * Scott H Kilau <Scott_Kilau at digi dot com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the
- * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- */
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/pci.h>
- #include <linux/slab.h>
- #include <linux/sched.h>
- #include "dgnc_driver.h"
- #include "dgnc_pci.h"
- #include "dgnc_mgmt.h"
- #include "dgnc_tty.h"
- #include "dgnc_cls.h"
- #include "dgnc_neo.h"
- #include "dgnc_sysfs.h"
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Digi International, http://www.digi.com");
- MODULE_DESCRIPTION("Driver for the Digi International Neo and Classic PCI based product line");
- MODULE_SUPPORTED_DEVICE("dgnc");
- /**************************************************************************
- *
- * protos for this file
- *
- */
- static int dgnc_start(void);
- static int dgnc_finalize_board_init(struct dgnc_board *brd);
- static int dgnc_found_board(struct pci_dev *pdev, int id);
- static void dgnc_cleanup_board(struct dgnc_board *brd);
- static void dgnc_poll_handler(ulong dummy);
- static int dgnc_init_one(struct pci_dev *pdev,
- const struct pci_device_id *ent);
- static void dgnc_do_remap(struct dgnc_board *brd);
- /*
- * File operations permitted on Control/Management major.
- */
- static const struct file_operations dgnc_BoardFops = {
- .owner = THIS_MODULE,
- .unlocked_ioctl = dgnc_mgmt_ioctl,
- .open = dgnc_mgmt_open,
- .release = dgnc_mgmt_close
- };
- /*
- * Globals
- */
- uint dgnc_NumBoards;
- struct dgnc_board *dgnc_Board[MAXBOARDS];
- DEFINE_SPINLOCK(dgnc_global_lock);
- DEFINE_SPINLOCK(dgnc_poll_lock); /* Poll scheduling lock */
- uint dgnc_Major;
- int dgnc_poll_tick = 20; /* Poll interval - 20 ms */
- /*
- * Static vars.
- */
- static struct class *dgnc_class;
- /*
- * Poller stuff
- */
- static ulong dgnc_poll_time; /* Time of next poll */
- static uint dgnc_poll_stop; /* Used to tell poller to stop */
- static struct timer_list dgnc_poll_timer;
- static const struct pci_device_id dgnc_pci_tbl[] = {
- {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_4_DID), .driver_data = 0},
- {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_4_422_DID), .driver_data = 1},
- {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_8_DID), .driver_data = 2},
- {PCI_DEVICE(DIGI_VID, PCI_DEVICE_CLASSIC_8_422_DID), .driver_data = 3},
- {0,}
- };
- MODULE_DEVICE_TABLE(pci, dgnc_pci_tbl);
- struct board_id {
- unsigned char *name;
- uint maxports;
- unsigned int is_pci_express;
- };
- static struct board_id dgnc_Ids[] = {
- { PCI_DEVICE_CLASSIC_4_PCI_NAME, 4, 0 },
- { PCI_DEVICE_CLASSIC_4_422_PCI_NAME, 4, 0 },
- { PCI_DEVICE_CLASSIC_8_PCI_NAME, 8, 0 },
- { PCI_DEVICE_CLASSIC_8_422_PCI_NAME, 8, 0 },
- { PCI_DEVICE_NEO_4_PCI_NAME, 4, 0 },
- { PCI_DEVICE_NEO_8_PCI_NAME, 8, 0 },
- { PCI_DEVICE_NEO_2DB9_PCI_NAME, 2, 0 },
- { PCI_DEVICE_NEO_2DB9PRI_PCI_NAME, 2, 0 },
- { PCI_DEVICE_NEO_2RJ45_PCI_NAME, 2, 0 },
- { PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME, 2, 0 },
- { PCI_DEVICE_NEO_1_422_PCI_NAME, 1, 0 },
- { PCI_DEVICE_NEO_1_422_485_PCI_NAME, 1, 0 },
- { PCI_DEVICE_NEO_2_422_485_PCI_NAME, 2, 0 },
- { PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME, 8, 1 },
- { PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME, 4, 1 },
- { PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME, 4, 1 },
- { PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME, 8, 1 },
- { NULL, 0, 0 }
- };
- static struct pci_driver dgnc_driver = {
- .name = "dgnc",
- .probe = dgnc_init_one,
- .id_table = dgnc_pci_tbl,
- };
- /************************************************************************
- *
- * Driver load/unload functions
- *
- ************************************************************************/
- /*
- * dgnc_cleanup_module()
- *
- * Module unload. This is where it all ends.
- */
- static void dgnc_cleanup_module(void)
- {
- int i;
- unsigned long flags;
- spin_lock_irqsave(&dgnc_poll_lock, flags);
- dgnc_poll_stop = 1;
- spin_unlock_irqrestore(&dgnc_poll_lock, flags);
- /* Turn off poller right away. */
- del_timer_sync(&dgnc_poll_timer);
- dgnc_remove_driver_sysfiles(&dgnc_driver);
- device_destroy(dgnc_class, MKDEV(dgnc_Major, 0));
- class_destroy(dgnc_class);
- unregister_chrdev(dgnc_Major, "dgnc");
- for (i = 0; i < dgnc_NumBoards; ++i) {
- dgnc_remove_ports_sysfiles(dgnc_Board[i]);
- dgnc_tty_uninit(dgnc_Board[i]);
- dgnc_cleanup_board(dgnc_Board[i]);
- }
- dgnc_tty_post_uninit();
- if (dgnc_NumBoards)
- pci_unregister_driver(&dgnc_driver);
- }
- /*
- * init_module()
- *
- * Module load. This is where it all starts.
- */
- static int __init dgnc_init_module(void)
- {
- int rc;
- /*
- * Initialize global stuff
- */
- rc = dgnc_start();
- if (rc < 0)
- return rc;
- /*
- * Find and configure all the cards
- */
- rc = pci_register_driver(&dgnc_driver);
- /*
- * If something went wrong in the scan, bail out of driver.
- */
- if (rc < 0) {
- /* Only unregister if it was actually registered. */
- if (dgnc_NumBoards)
- pci_unregister_driver(&dgnc_driver);
- else
- pr_warn("WARNING: dgnc driver load failed. No Digi Neo or Classic boards found.\n");
- dgnc_cleanup_module();
- } else {
- dgnc_create_driver_sysfiles(&dgnc_driver);
- }
- return rc;
- }
- module_init(dgnc_init_module);
- module_exit(dgnc_cleanup_module);
- /*
- * Start of driver.
- */
- static int dgnc_start(void)
- {
- int rc = 0;
- unsigned long flags;
- struct device *dev;
- /* make sure timer is initialized before we do anything else */
- init_timer(&dgnc_poll_timer);
- /*
- * Register our base character device into the kernel.
- * This allows the download daemon to connect to the downld device
- * before any of the boards are init'ed.
- *
- * Register management/dpa devices
- */
- rc = register_chrdev(0, "dgnc", &dgnc_BoardFops);
- if (rc < 0) {
- pr_err(DRVSTR ": Can't register dgnc driver device (%d)\n", rc);
- return rc;
- }
- dgnc_Major = rc;
- dgnc_class = class_create(THIS_MODULE, "dgnc_mgmt");
- if (IS_ERR(dgnc_class)) {
- rc = PTR_ERR(dgnc_class);
- pr_err(DRVSTR ": Can't create dgnc_mgmt class (%d)\n", rc);
- goto failed_class;
- }
- dev = device_create(dgnc_class, NULL,
- MKDEV(dgnc_Major, 0),
- NULL, "dgnc_mgmt");
- if (IS_ERR(dev)) {
- rc = PTR_ERR(dev);
- pr_err(DRVSTR ": Can't create device (%d)\n", rc);
- goto failed_device;
- }
- /*
- * Init any global tty stuff.
- */
- rc = dgnc_tty_preinit();
- if (rc < 0) {
- pr_err(DRVSTR ": tty preinit - not enough memory (%d)\n", rc);
- goto failed_tty;
- }
- /* Start the poller */
- spin_lock_irqsave(&dgnc_poll_lock, flags);
- setup_timer(&dgnc_poll_timer, dgnc_poll_handler, 0);
- dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick);
- dgnc_poll_timer.expires = dgnc_poll_time;
- spin_unlock_irqrestore(&dgnc_poll_lock, flags);
- add_timer(&dgnc_poll_timer);
- return 0;
- failed_tty:
- device_destroy(dgnc_class, MKDEV(dgnc_Major, 0));
- failed_device:
- class_destroy(dgnc_class);
- failed_class:
- unregister_chrdev(dgnc_Major, "dgnc");
- return rc;
- }
- /* returns count (>= 0), or negative on error */
- static int dgnc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
- {
- int rc;
- /* wake up and enable device */
- rc = pci_enable_device(pdev);
- if (rc < 0) {
- rc = -EIO;
- } else {
- rc = dgnc_found_board(pdev, ent->driver_data);
- if (rc == 0)
- dgnc_NumBoards++;
- }
- return rc;
- }
- /*
- * dgnc_cleanup_board()
- *
- * Free all the memory associated with a board
- */
- static void dgnc_cleanup_board(struct dgnc_board *brd)
- {
- int i = 0;
- if (!brd || brd->magic != DGNC_BOARD_MAGIC)
- return;
- switch (brd->device) {
- case PCI_DEVICE_CLASSIC_4_DID:
- case PCI_DEVICE_CLASSIC_8_DID:
- case PCI_DEVICE_CLASSIC_4_422_DID:
- case PCI_DEVICE_CLASSIC_8_422_DID:
- /* Tell card not to interrupt anymore. */
- outb(0, brd->iobase + 0x4c);
- break;
- default:
- break;
- }
- if (brd->irq)
- free_irq(brd->irq, brd);
- tasklet_kill(&brd->helper_tasklet);
- if (brd->re_map_membase) {
- iounmap(brd->re_map_membase);
- brd->re_map_membase = NULL;
- }
- if (brd->msgbuf_head) {
- unsigned long flags;
- spin_lock_irqsave(&dgnc_global_lock, flags);
- brd->msgbuf = NULL;
- dev_dbg(&brd->pdev->dev, "%s\n", brd->msgbuf_head);
- kfree(brd->msgbuf_head);
- brd->msgbuf_head = NULL;
- spin_unlock_irqrestore(&dgnc_global_lock, flags);
- }
- /* Free all allocated channels structs */
- for (i = 0; i < MAXPORTS ; i++) {
- if (brd->channels[i]) {
- kfree(brd->channels[i]->ch_rqueue);
- kfree(brd->channels[i]->ch_equeue);
- kfree(brd->channels[i]->ch_wqueue);
- kfree(brd->channels[i]);
- brd->channels[i] = NULL;
- }
- }
- dgnc_Board[brd->boardnum] = NULL;
- kfree(brd);
- }
- /*
- * dgnc_found_board()
- *
- * A board has been found, init it.
- */
- static int dgnc_found_board(struct pci_dev *pdev, int id)
- {
- struct dgnc_board *brd;
- unsigned int pci_irq;
- int i = 0;
- int rc = 0;
- unsigned long flags;
- /* get the board structure and prep it */
- dgnc_Board[dgnc_NumBoards] = kzalloc(sizeof(*brd), GFP_KERNEL);
- brd = dgnc_Board[dgnc_NumBoards];
- if (!brd)
- return -ENOMEM;
- /* make a temporary message buffer for the boot messages */
- brd->msgbuf_head = kcalloc(8192, sizeof(u8), GFP_KERNEL);
- brd->msgbuf = brd->msgbuf_head;
- if (!brd->msgbuf) {
- kfree(brd);
- return -ENOMEM;
- }
- /* store the info for the board we've found */
- brd->magic = DGNC_BOARD_MAGIC;
- brd->boardnum = dgnc_NumBoards;
- brd->vendor = dgnc_pci_tbl[id].vendor;
- brd->device = dgnc_pci_tbl[id].device;
- brd->pdev = pdev;
- brd->pci_bus = pdev->bus->number;
- brd->pci_slot = PCI_SLOT(pdev->devfn);
- brd->name = dgnc_Ids[id].name;
- brd->maxports = dgnc_Ids[id].maxports;
- if (dgnc_Ids[i].is_pci_express)
- brd->bd_flags |= BD_IS_PCI_EXPRESS;
- brd->dpastatus = BD_NOFEP;
- init_waitqueue_head(&brd->state_wait);
- spin_lock_init(&brd->bd_lock);
- spin_lock_init(&brd->bd_intr_lock);
- brd->state = BOARD_FOUND;
- for (i = 0; i < MAXPORTS; i++)
- brd->channels[i] = NULL;
- /* store which card & revision we have */
- pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor);
- pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice);
- pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev);
- pci_irq = pdev->irq;
- brd->irq = pci_irq;
- switch (brd->device) {
- case PCI_DEVICE_CLASSIC_4_DID:
- case PCI_DEVICE_CLASSIC_8_DID:
- case PCI_DEVICE_CLASSIC_4_422_DID:
- case PCI_DEVICE_CLASSIC_8_422_DID:
- brd->dpatype = T_CLASSIC | T_PCIBUS;
- /*
- * For PCI ClassicBoards
- * PCI Local Address (i.e. "resource" number) space
- * 0 PLX Memory Mapped Config
- * 1 PLX I/O Mapped Config
- * 2 I/O Mapped UARTs and Status
- * 3 Memory Mapped VPD
- * 4 Memory Mapped UARTs and Status
- */
- /* get the PCI Base Address Registers */
- brd->membase = pci_resource_start(pdev, 4);
- if (!brd->membase) {
- dev_err(&brd->pdev->dev,
- "Card has no PCI IO resources, failing.\n");
- return -ENODEV;
- }
- brd->membase_end = pci_resource_end(pdev, 4);
- if (brd->membase & 1)
- brd->membase &= ~3;
- else
- brd->membase &= ~15;
- brd->iobase = pci_resource_start(pdev, 1);
- brd->iobase_end = pci_resource_end(pdev, 1);
- brd->iobase = ((unsigned int)(brd->iobase)) & 0xFFFE;
- /* Assign the board_ops struct */
- brd->bd_ops = &dgnc_cls_ops;
- brd->bd_uart_offset = 0x8;
- brd->bd_dividend = 921600;
- dgnc_do_remap(brd);
- /* Get and store the board VPD, if it exists */
- brd->bd_ops->vpd(brd);
- /*
- * Enable Local Interrupt 1 (0x1),
- * Local Interrupt 1 Polarity Active high (0x2),
- * Enable PCI interrupt (0x40)
- */
- outb(0x43, brd->iobase + 0x4c);
- break;
- case PCI_DEVICE_NEO_4_DID:
- case PCI_DEVICE_NEO_8_DID:
- case PCI_DEVICE_NEO_2DB9_DID:
- case PCI_DEVICE_NEO_2DB9PRI_DID:
- case PCI_DEVICE_NEO_2RJ45_DID:
- case PCI_DEVICE_NEO_2RJ45PRI_DID:
- case PCI_DEVICE_NEO_1_422_DID:
- case PCI_DEVICE_NEO_1_422_485_DID:
- case PCI_DEVICE_NEO_2_422_485_DID:
- case PCI_DEVICE_NEO_EXPRESS_8_DID:
- case PCI_DEVICE_NEO_EXPRESS_4_DID:
- case PCI_DEVICE_NEO_EXPRESS_4RJ45_DID:
- case PCI_DEVICE_NEO_EXPRESS_8RJ45_DID:
- /*
- * This chip is set up 100% when we get to it.
- * No need to enable global interrupts or anything.
- */
- if (brd->bd_flags & BD_IS_PCI_EXPRESS)
- brd->dpatype = T_NEO_EXPRESS | T_PCIBUS;
- else
- brd->dpatype = T_NEO | T_PCIBUS;
- /* get the PCI Base Address Registers */
- brd->membase = pci_resource_start(pdev, 0);
- brd->membase_end = pci_resource_end(pdev, 0);
- if (brd->membase & 1)
- brd->membase &= ~3;
- else
- brd->membase &= ~15;
- /* Assign the board_ops struct */
- brd->bd_ops = &dgnc_neo_ops;
- brd->bd_uart_offset = 0x200;
- brd->bd_dividend = 921600;
- dgnc_do_remap(brd);
- if (brd->re_map_membase) {
- /* Read and store the dvid after remapping */
- brd->dvid = readb(brd->re_map_membase + 0x8D);
- /* Get and store the board VPD, if it exists */
- brd->bd_ops->vpd(brd);
- }
- break;
- default:
- dev_err(&brd->pdev->dev,
- "Didn't find any compatible Neo/Classic PCI boards.\n");
- return -ENXIO;
- }
- /*
- * Do tty device initialization.
- */
- rc = dgnc_tty_register(brd);
- if (rc < 0) {
- pr_err(DRVSTR ": Can't register tty devices (%d)\n", rc);
- goto failed;
- }
- rc = dgnc_finalize_board_init(brd);
- if (rc < 0) {
- pr_err(DRVSTR ": Can't finalize board init (%d)\n", rc);
- goto failed;
- }
- rc = dgnc_tty_init(brd);
- if (rc < 0) {
- pr_err(DRVSTR ": Can't init tty devices (%d)\n", rc);
- goto failed;
- }
- brd->state = BOARD_READY;
- brd->dpastatus = BD_RUNNING;
- dgnc_create_ports_sysfiles(brd);
- /* init our poll helper tasklet */
- tasklet_init(&brd->helper_tasklet,
- brd->bd_ops->tasklet,
- (unsigned long)brd);
- spin_lock_irqsave(&dgnc_global_lock, flags);
- brd->msgbuf = NULL;
- dev_dbg(&brd->pdev->dev, "%s\n", brd->msgbuf_head);
- kfree(brd->msgbuf_head);
- brd->msgbuf_head = NULL;
- spin_unlock_irqrestore(&dgnc_global_lock, flags);
- wake_up_interruptible(&brd->state_wait);
- return 0;
- failed:
- dgnc_tty_uninit(brd);
- brd->state = BOARD_FAILED;
- brd->dpastatus = BD_NOFEP;
- return -ENXIO;
- }
- static int dgnc_finalize_board_init(struct dgnc_board *brd)
- {
- int rc = 0;
- if (!brd || brd->magic != DGNC_BOARD_MAGIC)
- return -ENODEV;
- if (brd->irq) {
- rc = request_irq(brd->irq, brd->bd_ops->intr,
- IRQF_SHARED, "DGNC", brd);
- if (rc) {
- dev_err(&brd->pdev->dev,
- "Failed to hook IRQ %d\n", brd->irq);
- brd->state = BOARD_FAILED;
- brd->dpastatus = BD_NOFEP;
- rc = -ENODEV;
- }
- }
- return rc;
- }
- /*
- * Remap PCI memory.
- */
- static void dgnc_do_remap(struct dgnc_board *brd)
- {
- if (!brd || brd->magic != DGNC_BOARD_MAGIC)
- return;
- brd->re_map_membase = ioremap(brd->membase, 0x1000);
- }
- /*****************************************************************************
- *
- * Function:
- *
- * dgnc_poll_handler
- *
- * Author:
- *
- * Scott H Kilau
- *
- * Parameters:
- *
- * dummy -- ignored
- *
- * Return Values:
- *
- * none
- *
- * Description:
- *
- * As each timer expires, it determines (a) whether the "transmit"
- * waiter needs to be woken up, and (b) whether the poller needs to
- * be rescheduled.
- *
- ******************************************************************************/
- static void dgnc_poll_handler(ulong dummy)
- {
- struct dgnc_board *brd;
- unsigned long flags;
- int i;
- unsigned long new_time;
- /* Go thru each board, kicking off a tasklet for each if needed */
- for (i = 0; i < dgnc_NumBoards; i++) {
- brd = dgnc_Board[i];
- spin_lock_irqsave(&brd->bd_lock, flags);
- /* If board is in a failed state don't schedule a tasklet */
- if (brd->state == BOARD_FAILED) {
- spin_unlock_irqrestore(&brd->bd_lock, flags);
- continue;
- }
- /* Schedule a poll helper task */
- tasklet_schedule(&brd->helper_tasklet);
- spin_unlock_irqrestore(&brd->bd_lock, flags);
- }
- /*
- * Schedule ourself back at the nominal wakeup interval.
- */
- spin_lock_irqsave(&dgnc_poll_lock, flags);
- dgnc_poll_time += dgnc_jiffies_from_ms(dgnc_poll_tick);
- new_time = dgnc_poll_time - jiffies;
- if ((ulong)new_time >= 2 * dgnc_poll_tick)
- dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick);
- setup_timer(&dgnc_poll_timer, dgnc_poll_handler, 0);
- dgnc_poll_timer.expires = dgnc_poll_time;
- spin_unlock_irqrestore(&dgnc_poll_lock, flags);
- if (!dgnc_poll_stop)
- add_timer(&dgnc_poll_timer);
- }
|