/* * Copyright 2009-2010, François Revol, . * Sponsored by TuneTracker Systems. * Based on the Haiku usb_serial driver which is: * * Copyright (c) 2007-2008 by Michael Lotz * Heavily based on the original usb_serial driver which is: * * Copyright (c) 2003 by Siarzhuk Zharski * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include "Driver.h" #include "SerialDevice.h" int32 api_version = B_CUR_DRIVER_API_VERSION; static const char *sDeviceBaseName = DEVFS_BASE; SerialDevice *gSerialDevices[DEVICES_COUNT]; char *gDeviceNames[DEVICES_COUNT + 1]; isa_module_info *gISAModule = NULL; pci_module_info *gPCIModule = NULL; tty_module_info *gTTYModule = NULL; dpc_module_info *gDPCModule = NULL; void* gDPCHandle = NULL; sem_id gDriverLock = -1; bool gHandleISA = false; uint32 gKernelDebugPort = 0x3f8; // 24 MHz clock static const uint32 sDefaultRates[] = { 0, //B0 2304, //B50 1536, //B75 1047, //B110 857, //B134 768, //B150 512, //B200 384, //B300 192, //B600 96, //B1200 64, //B1800 48, //B2400 24, //B4800 12, //B9600 6, //B19200 3, //B38400 2, //B57600 1, //B115200 0, //B230400 4, //B31250 0, //921600 !? }; // 8MHz clock on serial3 and 4 on the BeBox #if 0 static const uint32 sBeBoxRates[] = { 0, //B0 //... TODO }; #endif // XXX: should really be generated from metadata (CSV ?) static const struct serial_support_descriptor sSupportedDevices[] = { #ifdef HANDLE_ISA_COM // ISA devices { B_ISA_BUS, "Generic 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16550 } }, #endif // PCI devices // vendor/device matches first /* // vendor: OxfordSemi #define VN "OxfordSemi" // http://www.softio.com/ox16pci954ds.pdf { B_PCI_BUS, "OxfordSemi 16950 Serial Port", sDefaultRates, NULL, { 32, 32, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16950, 0x1415, 0x9501, PCI_INVAL, PCI_INVAL } }, // http://www.softio.com/ox16pci952ds.pdf { B_PCI_BUS, "OxfordSemi 16950 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16950, 0x1415, 0x9521, PCI_INVAL, PCI_INVAL } }, */ // vendor: NetMos #define VN "MosChip" // used in Manhattan cards // 1 function / port // http://www.moschip.com/data/products/MCS9865/Data%20Sheet_9865.pdf { B_PCI_BUS, VN" 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8, 0, 0, 0 }, { PCI_simple_communications, PCI_serial, PCI_serial_16550, 0x9710, 0x9865, PCI_INVAL, PCI_INVAL } }, // single function with all ports // only BAR 0 & 1 are UART // http://www.moschip.com/data/products/NM9835/Data%20Sheet_9835.pdf { B_PCI_BUS, VN" 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8, (uint8)~0x3, 2, 0x000f }, { PCI_simple_communications, PCI_serial, PCI_serial_16550, 0x9710, 0x9835, PCI_INVAL, PCI_INVAL } }, #undef VN // generic fallback matches /* { B_PCI_BUS, "Generic XT Serial Port", NULL }, { PCI_INVAL, PCI_INVAL, PCI_simple_communications, PCI_serial, PCI_serial_xt, PCI_INVAL, PCI_INVAL } }, { B_PCI_BUS, "Generic 16450 Serial Port", NULL }, { PCI_INVAL, PCI_INVAL, PCI_simple_communications, PCI_serial, PCI_serial_16450, PCI_INVAL, PCI_INVAL } }, */ { B_PCI_BUS, "Generic 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16550, PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } }, { B_PCI_BUS, "Generic 16650 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16650, PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } }, { B_PCI_BUS, "Generic 16750 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16750, PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } }, { B_PCI_BUS, "Generic 16850 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16850, PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } }, { B_PCI_BUS, "Generic 16950 Serial Port", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_serial, PCI_serial_16950, PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } }, // non PCI_serial devices // beos zz driver supported that one { B_PCI_BUS, "Lucent Modem", sDefaultRates, NULL, { 8, 8, 8 }, { PCI_simple_communications, PCI_simple_communications_other, 0x00, 0x11C1, 0x0480, PCI_INVAL, PCI_INVAL } }, { B_PCI_BUS, NULL, NULL, NULL, {0}, {0} } }; // hardcoded ISA ports static struct isa_ports { uint32 ioBase; uint32 irq; } sHardcodedPorts[] = { { 0x3f8, 4 }, { 0x2f8, 3 }, { 0x3e8, 4 }, { 0x2e8, 3 }, }; #if 0 status_t pc_serial_device_added(pc_device device, void **cookie) { TRACE_FUNCALLS("> pc_serial_device_added(0x%08x, 0x%08x)\n", device, cookie); status_t status = B_OK; const pc_device_descriptor *descriptor = gUSBModule->get_device_descriptor(device); TRACE_ALWAYS("probing device: 0x%04x/0x%04x\n", descriptor->vendor_id, descriptor->product_id); *cookie = NULL; SerialDevice *serialDevice = SerialDevice::MakeDevice(device, descriptor->vendor_id, descriptor->product_id); const pc_configuration_info *configuration = gUSBModule->get_nth_configuration(device, 0); if (!configuration) return B_ERROR; status = serialDevice->AddDevice(configuration); if (status < B_OK) { delete serialDevice; return status; } acquire_sem(gDriverLock); for (int32 i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i] != NULL) continue; status = serialDevice->Init(); if (status < B_OK) { delete serialDevice; return status; } gSerialDevices[i] = serialDevice; *cookie = serialDevice; release_sem(gDriverLock); TRACE_ALWAYS("%s (0x%04x/0x%04x) added\n", serialDevice->Description(), descriptor->vendor_id, descriptor->product_id); return B_OK; } release_sem(gDriverLock); return B_ERROR; } status_t pc_serial_device_removed(void *cookie) { TRACE_FUNCALLS("> pc_serial_device_removed(0x%08x)\n", cookie); acquire_sem(gDriverLock); SerialDevice *device = (SerialDevice *)cookie; for (int32 i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i] == device) { if (device->IsOpen()) { // the device will be deleted upon being freed device->Removed(); } else { delete device; gSerialDevices[i] = NULL; } break; } } release_sem(gDriverLock); TRACE_FUNCRET("< pc_serial_device_removed() returns\n"); return B_OK; } #endif //#pragma mark - status_t pc_serial_insert_device(SerialDevice *device) { status_t status = B_OK; //XXX fix leaks! acquire_sem(gDriverLock); for (int32 i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i] != NULL) continue; status = device->Init(); if (status < B_OK) { delete device; //return status; break; } gSerialDevices[i] = device; release_sem(gDriverLock); TRACE_ALWAYS("%s added\n", device->Description()); return B_OK; } release_sem(gDriverLock); return B_ERROR; } // until we support ISA device enumeration from PnP BIOS or ACPI, // we have to probe the 4 default COM ports... status_t scan_isa_hardcoded() { #ifdef HANDLE_ISA_COM int i; bool serialDebug = get_safemode_boolean("serial_debug_output", true); for (i = 0; i < 4; i++) { // skip the port used for kernel debugging... if (serialDebug && sHardcodedPorts[i].ioBase == gKernelDebugPort) { TRACE_ALWAYS("Skipping port %d as it is used for kernel debug.\n", i); continue; } SerialDevice *device; device = new(std::nothrow) SerialDevice(&sSupportedDevices[0], sHardcodedPorts[i].ioBase, sHardcodedPorts[i].irq); if (device != NULL && device->Probe()) pc_serial_insert_device(device); else delete device; } #endif return B_OK; } status_t scan_pci() { pci_info info; int ix; TRACE_ALWAYS("scanning PCI bus (alt)...\n"); // probe PCI devices for (ix = 0; (*gPCIModule->get_nth_pci_info)(ix, &info) == B_OK; ix++) { // sanity check if ((info.header_type & PCI_header_type_mask) != PCI_header_type_generic) continue; /* TRACE_ALWAYS("probing PCI device %2d [%x|%x|%x] %04x:%04x\n", ix, info.class_base, info.class_sub, info.class_api, info.vendor_id, info.device_id); */ const struct serial_support_descriptor *supported = NULL; for (int i = 0; sSupportedDevices[i].name; i++) { if (sSupportedDevices[i].bus != B_PCI_BUS) continue; if (info.class_base != sSupportedDevices[i].match.class_base) continue; if (info.class_sub != sSupportedDevices[i].match.class_sub) continue; if (info.class_api != sSupportedDevices[i].match.class_api) continue; if (sSupportedDevices[i].match.vendor_id != PCI_INVAL && info.vendor_id != sSupportedDevices[i].match.vendor_id) continue; if (sSupportedDevices[i].match.device_id != PCI_INVAL && info.device_id != sSupportedDevices[i].match.device_id) continue; supported = &sSupportedDevices[i]; break; } if (supported == NULL) continue; TRACE_ALWAYS("found PCI device %2d [%x|%x|%x] %04x:%04x as %s\n", ix, info.class_base, info.class_sub, info.class_api, info.vendor_id, info.device_id, supported->name); // XXX: interrupt_line doesn't seem to TRACE_ALWAYS("irq line %d, pin %d\n", info.u.h0.interrupt_line, info.u.h0.interrupt_pin); int irq = info.u.h0.interrupt_line; SerialDevice *master = NULL; uint8 portCount = 0; uint32 maxPorts = DEVICES_COUNT; if (supported->constraints.maxports) { maxPorts = supported->constraints.maxports; TRACE_ALWAYS("card supports up to %d ports\n", maxPorts); } if (supported->constraints.subsystem_id_mask) { uint32 id = info.u.h0.subsystem_id; uint32 mask = supported->constraints.subsystem_id_mask; id &= mask; //TRACE_ALWAYS("mask: %lx, masked: %lx\n", mask, id); while (!(mask & 0x1)) { mask >>= 1; id >>= 1; } maxPorts = (uint8)id; TRACE_ALWAYS("subsystem id tells card has %d ports\n", maxPorts); } // find I/O ports for (int r = 0; r < 6; r++) { uint32 regbase = info.u.h0.base_registers[r]; uint32 reglen = info.u.h0.base_register_sizes[r]; /**/ TRACE("ranges[%d] at 0x%08lx len 0x%lx flags 0x%02x\n", r, regbase, reglen, info.u.h0.base_register_flags[r]); /**/ // empty if (reglen == 0) continue; // not I/O if ((info.u.h0.base_register_flags[r] & PCI_address_space) == 0) continue; // the range for sure doesn't contain any UART if (supported->constraints.ignoremask & (1 << r)) { TRACE_ALWAYS("ignored regs at 0x%08lx len 0x%lx\n", regbase, reglen); continue; } TRACE_ALWAYS("regs at 0x%08lx len 0x%lx\n", regbase, reglen); //&PCI_address_io_mask if (reglen < supported->constraints.minsize) continue; if (reglen > supported->constraints.maxsize) continue; SerialDevice *device; uint32 ioport = regbase; next_split_alt: // no more to split if ((ioport - regbase) >= reglen) continue; if (portCount >= maxPorts) break; TRACE_ALWAYS("inserting device at io 0x%04lx as %s\n", ioport, supported->name); /**/ device = new(std::nothrow) SerialDevice(supported, ioport, irq, master); if (device == NULL) { TRACE_ALWAYS("can't allocate device\n"); continue; } if (pc_serial_insert_device(device) < B_OK) { TRACE_ALWAYS("can't insert device\n"); continue; } /**/ if (master == NULL) master = device; ioport += supported->constraints.split; portCount++; goto next_split_alt; // try next part of the I/O range now } } return B_OK; } static void check_kernel_debug_port() { void *handle; long int value; handle = load_driver_settings("kernel"); if (handle == NULL) return; const char *str = get_driver_parameter(handle, "serial_debug_port", NULL, NULL); if (str != NULL) { value = strtol(str, NULL, 0); if (value >= 4) // XXX: actually should be MAX_SERIAL_PORTS... gKernelDebugPort = (uint32)value; else if (value >= 0) // XXX: we should use the kernel_arg's table... gKernelDebugPort = sHardcodedPorts[value].ioBase; } /* TODO: actually handle this in the kernel debugger too! bool enabled = get_driver_boolean_parameter(handle, "serial_debug_output", false, true); if (!enabled) gKernelDebugPort = 0; */ unload_driver_settings(handle); } //#pragma mark - /* init_hardware - called once the first time the driver is loaded */ status_t init_hardware() { TRACE("init_hardware\n"); return B_OK; } /* init_driver - called every time the driver is loaded. */ status_t init_driver() { status_t status; load_settings(); create_log_file(); TRACE_FUNCALLS("> init_driver()\n"); status = get_module(B_DPC_MODULE_NAME, (module_info **)&gDPCModule); if (status < B_OK) goto err_dpc; status = get_module(B_TTY_MODULE_NAME, (module_info **)&gTTYModule); if (status < B_OK) goto err_tty; status = get_module(B_PCI_MODULE_NAME, (module_info **)&gPCIModule); if (status < B_OK) goto err_pci; status = get_module(B_ISA_MODULE_NAME, (module_info **)&gISAModule); if (status < B_OK) goto err_isa; status = gDPCModule->new_dpc_queue(&gDPCHandle, "pc_serial irq", B_REAL_TIME_PRIORITY); if (status != B_OK) goto err_dpcq; for (int32 i = 0; i < DEVICES_COUNT; i++) gSerialDevices[i] = NULL; gDeviceNames[0] = NULL; gDriverLock = create_sem(1, DRIVER_NAME"_devices_table_lock"); if (gDriverLock < B_OK) { status = gDriverLock; goto err_sem; } status = ENOENT; check_kernel_debug_port(); scan_isa_hardcoded(); scan_pci(); // XXX: ISA cards // XXX: pcmcia TRACE_FUNCRET("< init_driver() returns\n"); return B_OK; //err_none: delete_sem(gDriverLock); err_sem: gDPCModule->delete_dpc_queue(gDPCHandle); gDPCHandle = NULL; err_dpcq: put_module(B_ISA_MODULE_NAME); err_isa: put_module(B_PCI_MODULE_NAME); err_pci: put_module(B_TTY_MODULE_NAME); err_tty: put_module(B_DPC_MODULE_NAME); err_dpc: TRACE_FUNCRET("< init_driver() returns %s\n", strerror(status)); return status; } /* uninit_driver - called every time the driver is unloaded */ void uninit_driver() { TRACE_FUNCALLS("> uninit_driver()\n"); //gUSBModule->uninstall_notify(DRIVER_NAME); acquire_sem(gDriverLock); for (int32 i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i]) { /* if (gSerialDevices[i]->Master() == gSerialDevices[i]) remove_io_interrupt_handler(gSerialDevices[i]->IRQ(), pc_serial_interrupt, gSerialDevices[i]); */ delete gSerialDevices[i]; gSerialDevices[i] = NULL; } } for (int32 i = 0; gDeviceNames[i]; i++) free(gDeviceNames[i]); delete_sem(gDriverLock); gDPCModule->delete_dpc_queue(gDPCHandle); gDPCHandle = NULL; put_module(B_ISA_MODULE_NAME); put_module(B_PCI_MODULE_NAME); put_module(B_TTY_MODULE_NAME); put_module(B_DPC_MODULE_NAME); TRACE_FUNCRET("< uninit_driver() returns\n"); } bool pc_serial_service(struct tty *tty, uint32 op, void *buffer, size_t length) { TRACE_FUNCALLS("> pc_serial_service(%p, 0x%08lx, %p, %lu)\n", tty, op, buffer, length); for (int32 i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i] && gSerialDevices[i]->Service(tty, op, buffer, length)) { TRACE_FUNCRET("< pc_serial_service() returns: true\n"); return true; } } TRACE_FUNCRET("< pc_serial_service() returns: false\n"); return false; } static void pc_serial_dpc(void *arg) { SerialDevice *master = (SerialDevice *)arg; TRACE_FUNCALLS("> pc_serial_dpc(%p)\n", arg); master->InterruptHandler(); } int32 pc_serial_interrupt(void *arg) { SerialDevice *device = (SerialDevice *)arg; TRACE_FUNCALLS("> pc_serial_interrupt(%p)\n", arg); if (!device) return B_UNHANDLED_INTERRUPT; if (device->IsInterruptPending()) { status_t err; err = gDPCModule->queue_dpc(gDPCHandle, pc_serial_dpc, device); if (err != B_OK) dprintf(DRIVER_NAME ": error queing irq: %s\n", strerror(err)); else { TRACE_FUNCRET("< pc_serial_interrupt() returns: resched\n"); return B_INVOKE_SCHEDULER; } } TRACE_FUNCRET("< pc_serial_interrupt() returns: unhandled\n"); return B_UNHANDLED_INTERRUPT; } /* pc_serial_open - handle open() calls */ status_t pc_serial_open(const char *name, uint32 flags, void **cookie) { TRACE_FUNCALLS("> pc_serial_open(%s, 0x%08x, 0x%08x)\n", name, flags, cookie); acquire_sem(gDriverLock); status_t status = ENODEV; *cookie = NULL; int i = strtol(name + strlen(sDeviceBaseName), NULL, 10); if (i >= 0 && i < DEVICES_COUNT && gSerialDevices[i]) { status = gSerialDevices[i]->Open(flags); *cookie = gSerialDevices[i]; } release_sem(gDriverLock); TRACE_FUNCRET("< pc_serial_open() returns: 0x%08x\n", status); return status; } /* pc_serial_read - handle read() calls */ status_t pc_serial_read(void *cookie, off_t position, void *buffer, size_t *numBytes) { TRACE_FUNCALLS("> pc_serial_read(0x%08x, %lld, 0x%08x, %d)\n", cookie, position, buffer, *numBytes); SerialDevice *device = (SerialDevice *)cookie; return device->Read((char *)buffer, numBytes); } /* pc_serial_write - handle write() calls */ status_t pc_serial_write(void *cookie, off_t position, const void *buffer, size_t *numBytes) { TRACE_FUNCALLS("> pc_serial_write(0x%08x, %lld, 0x%08x, %d)\n", cookie, position, buffer, *numBytes); SerialDevice *device = (SerialDevice *)cookie; return device->Write((const char *)buffer, numBytes); } /* pc_serial_control - handle ioctl calls */ status_t pc_serial_control(void *cookie, uint32 op, void *arg, size_t length) { TRACE_FUNCALLS("> pc_serial_control(0x%08x, 0x%08x, 0x%08x, %d)\n", cookie, op, arg, length); SerialDevice *device = (SerialDevice *)cookie; return device->Control(op, arg, length); } /* pc_serial_select - handle select start */ status_t pc_serial_select(void *cookie, uint8 event, uint32 ref, selectsync *sync) { TRACE_FUNCALLS("> pc_serial_select(0x%08x, 0x%08x, 0x%08x, %p)\n", cookie, event, ref, sync); SerialDevice *device = (SerialDevice *)cookie; return device->Select(event, ref, sync); } /* pc_serial_deselect - handle select exit */ status_t pc_serial_deselect(void *cookie, uint8 event, selectsync *sync) { TRACE_FUNCALLS("> pc_serial_deselect(0x%08x, 0x%08x, %p)\n", cookie, event, sync); SerialDevice *device = (SerialDevice *)cookie; return device->DeSelect(event, sync); } /* pc_serial_close - handle close() calls */ status_t pc_serial_close(void *cookie) { TRACE_FUNCALLS("> pc_serial_close(0x%08x)\n", cookie); SerialDevice *device = (SerialDevice *)cookie; return device->Close(); } /* pc_serial_free - called after last device is closed, and all i/o complete. */ status_t pc_serial_free(void *cookie) { TRACE_FUNCALLS("> pc_serial_free(0x%08x)\n", cookie); SerialDevice *device = (SerialDevice *)cookie; acquire_sem(gDriverLock); status_t status = device->Free(); if (device->IsRemoved()) { for (int32 i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i] == device) { // the device is removed already but as it was open the // removed hook has not deleted the object delete device; gSerialDevices[i] = NULL; break; } } } release_sem(gDriverLock); return status; } /* publish_devices - null-terminated array of devices supported by this driver. */ const char ** publish_devices() { TRACE_FUNCALLS("> publish_devices()\n"); for (int32 i = 0; gDeviceNames[i]; i++) free(gDeviceNames[i]); int j = 0; acquire_sem(gDriverLock); for(int i = 0; i < DEVICES_COUNT; i++) { if (gSerialDevices[i]) { gDeviceNames[j] = (char *)malloc(strlen(sDeviceBaseName) + 4); if (gDeviceNames[j]) { sprintf(gDeviceNames[j], "%s%d", sDeviceBaseName, i); j++; } else TRACE_ALWAYS("publish_devices - no memory to allocate device names\n"); } } gDeviceNames[j] = NULL; release_sem(gDriverLock); return (const char **)&gDeviceNames[0]; } /* find_device - return poiter to device hooks structure for a given device */ device_hooks * find_device(const char *name) { static device_hooks deviceHooks = { pc_serial_open, /* -> open entry point */ pc_serial_close, /* -> close entry point */ pc_serial_free, /* -> free cookie */ pc_serial_control, /* -> control entry point */ pc_serial_read, /* -> read entry point */ pc_serial_write, /* -> write entry point */ pc_serial_select, /* -> select entry point */ pc_serial_deselect /* -> deselect entry point */ }; TRACE_FUNCALLS("> find_device(%s)\n", name); return &deviceHooks; }