* Copyright 2009-2010, François Revol, <revol@free.fr>.
* 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 <imker@gmx.li>
* Distributed under the terms of the MIT License.
*/
#include <KernelExport.h>
#include <dpc.h>
#include <Drivers.h>
#include <driver_settings.h>
#include <image.h>
#include <kernel/safemode.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#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;
static const uint32 sDefaultRates[] = {
0,
2304,
1536,
1047,
857,
768,
512,
384,
192,
96,
64,
48,
24,
12,
6,
3,
2,
1,
0,
4,
0,
};
#if 0
static const uint32 sBeBoxRates[] = {
0,
};
#endif
static const struct serial_support_descriptor sSupportedDevices[] = {
#ifdef HANDLE_ISA_COM
{ B_ISA_BUS, "Generic 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
{ PCI_simple_communications, PCI_serial, PCI_serial_16550 } },
#endif
// 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 } },
*/
#define VN "MosChip"
{ 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 } },
{ 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
{ 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 } },
{ 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} }
};
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()) {
device->Removed();
} else {
delete device;
gSerialDevices[i] = NULL;
}
break;
}
}
release_sem(gDriverLock);
TRACE_FUNCRET("< pc_serial_device_removed() returns\n");
return B_OK;
}
#endif
status_t
pc_serial_insert_device(SerialDevice *device)
{
status_t status = B_OK;
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;
break;
}
gSerialDevices[i] = device;
release_sem(gDriverLock);
TRACE_ALWAYS("%s added\n", device->Description());
return B_OK;
}
release_sem(gDriverLock);
return B_ERROR;
}
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++) {
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");
for (ix = 0; (*gPCIModule->get_nth_pci_info)(ix, &info) == B_OK; ix++) {
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);
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;
while (!(mask & 0x1)) {
mask >>= 1;
id >>= 1;
}
maxPorts = (uint8)id;
TRACE_ALWAYS("subsystem id tells card has %d ports\n", maxPorts);
}
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]);
if (reglen == 0)
continue;
if ((info.u.h0.base_register_flags[r] & PCI_address_space) == 0)
continue;
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);
if (reglen < supported->constraints.minsize)
continue;
if (reglen > supported->constraints.maxsize)
continue;
SerialDevice *device;
uint32 ioport = regbase;
next_split_alt:
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;
}
}
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)
gKernelDebugPort = (uint32)value;
else if (value >= 0)
gKernelDebugPort = sHardcodedPorts[value].ioBase;
}
bool enabled = get_driver_boolean_parameter(handle, "serial_debug_output",
false, true);
if (!enabled)
gKernelDebugPort = 0;
*/
unload_driver_settings(handle);
}
status_t
init_hardware()
{
TRACE("init_hardware\n");
return B_OK;
}
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();
TRACE_FUNCRET("< init_driver() returns\n");
return B_OK;
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;
}
void
uninit_driver()
{
TRACE_FUNCALLS("> uninit_driver()\n");
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;
}
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;
}
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);
}
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);
}
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);
}
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);
}
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);
}
status_t
pc_serial_close(void *cookie)
{
TRACE_FUNCALLS("> pc_serial_close(0x%08x)\n", cookie);
SerialDevice *device = (SerialDevice *)cookie;
return device->Close();
}
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) {
delete device;
gSerialDevices[i] = NULL;
break;
}
}
}
release_sem(gDriverLock);
return status;
}
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];
}
device_hooks *
find_device(const char *name)
{
static device_hooks deviceHooks = {
pc_serial_open,
pc_serial_close,
pc_serial_free,
pc_serial_control,
pc_serial_read,
pc_serial_write,
pc_serial_select,
pc_serial_deselect
};
TRACE_FUNCALLS("> find_device(%s)\n", name);
return &deviceHooks;
}