1/*
2 * Copyright 2009-2010, Fran��ois Revol, <revol@free.fr>.
3 * Sponsored by TuneTracker Systems.
4 * Based on the Haiku usb_serial driver which is:
5 *
6 * Copyright (c) 2007-2008 by Michael Lotz
7 * Heavily based on the original usb_serial driver which is:
8 *
9 * Copyright (c) 2003 by Siarzhuk Zharski <imker@gmx.li>
10 * Distributed under the terms of the MIT License.
11 */
12#include <KernelExport.h>
13#include <dpc.h>
14#include <Drivers.h>
15#include <driver_settings.h>
16#include <image.h>
17#include <kernel/safemode.h>
18#include <malloc.h>
19#include <stdio.h>
20#include <stdlib.h>
21
22#include "Driver.h"
23#include "SerialDevice.h"
24
25int32 api_version = B_CUR_DRIVER_API_VERSION;
26static const char *sDeviceBaseName = DEVFS_BASE;
27SerialDevice *gSerialDevices[DEVICES_COUNT];
28char *gDeviceNames[DEVICES_COUNT + 1];
29isa_module_info *gISAModule = NULL;
30pci_module_info *gPCIModule = NULL;
31tty_module_info *gTTYModule = NULL;
32dpc_module_info *gDPCModule = NULL;
33void* gDPCHandle = NULL;
34sem_id gDriverLock = -1;
35bool gHandleISA = false;
36uint32 gKernelDebugPort = 0x3f8;
37
38// 24 MHz clock
39static const uint32 sDefaultRates[] = {
40		0,		//B0
41		2304,	//B50
42		1536,	//B75
43		1047,	//B110
44		857,	//B134
45		768,	//B150
46		512,	//B200
47		384,	//B300
48		192,	//B600
49		96,		//B1200
50		64,		//B1800
51		48,		//B2400
52		24,		//B4800
53		12,		//B9600
54		6,		//B19200
55		3,		//B38400
56		2,		//B57600
57		1,		//B115200
58		0,		//B230400
59		4,		//B31250
60		0, //921600 !?
61};
62
63// 8MHz clock on serial3 and 4 on the BeBox
64#if 0
65static const uint32 sBeBoxRates[] = {
66		0,		//B0
67		//... TODO
68};
69#endif
70
71// XXX: should really be generated from metadata (CSV ?)
72
73static const struct serial_support_descriptor sSupportedDevices[] = {
74
75#ifdef HANDLE_ISA_COM
76	// ISA devices
77	{ B_ISA_BUS, "Generic 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
78	  { PCI_simple_communications, PCI_serial, PCI_serial_16550 } },
79#endif
80	// PCI devices
81
82	// vendor/device matches first
83
84/*
85	// vendor: OxfordSemi
86#define VN "OxfordSemi"
87	// http://www.softio.com/ox16pci954ds.pdf
88	{ B_PCI_BUS, "OxfordSemi 16950 Serial Port", sDefaultRates, NULL, { 32, 32, 8 },
89	  { PCI_simple_communications, PCI_serial, PCI_serial_16950,
90		0x1415, 0x9501, PCI_INVAL, PCI_INVAL } },
91
92	// http://www.softio.com/ox16pci952ds.pdf
93	{ B_PCI_BUS, "OxfordSemi 16950 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
94	  { PCI_simple_communications, PCI_serial, PCI_serial_16950,
95		0x1415, 0x9521, PCI_INVAL, PCI_INVAL } },
96*/
97
98
99	// vendor: NetMos
100#define VN "MosChip"
101
102	// used in Manhattan cards
103	// 1 function / port
104	// http://www.moschip.com/data/products/MCS9865/Data%20Sheet_9865.pdf
105	{ B_PCI_BUS, VN" 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8, 0, 0, 0 },
106	  { PCI_simple_communications, PCI_serial, PCI_serial_16550,
107		0x9710, 0x9865, PCI_INVAL, PCI_INVAL } },
108
109	// single function with all ports
110	// only BAR 0 & 1 are UART
111	// http://www.moschip.com/data/products/NM9835/Data%20Sheet_9835.pdf
112	{ B_PCI_BUS, VN" 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8, (uint8)~0x3, 2, 0x000f },
113	  { PCI_simple_communications, PCI_serial, PCI_serial_16550,
114		0x9710, 0x9835, PCI_INVAL, PCI_INVAL } },
115
116#undef VN
117
118
119
120	// generic fallback matches
121	/*
122	{ B_PCI_BUS, "Generic XT Serial Port", NULL },
123	  { PCI_INVAL, PCI_INVAL, PCI_simple_communications,
124		PCI_serial, PCI_serial_xt, PCI_INVAL, PCI_INVAL } },
125
126	{ B_PCI_BUS, "Generic 16450 Serial Port", NULL },
127	  { PCI_INVAL, PCI_INVAL, PCI_simple_communications,
128		PCI_serial, PCI_serial_16450, PCI_INVAL, PCI_INVAL } },
129
130	*/
131	{ B_PCI_BUS, "Generic 16550 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
132	  { PCI_simple_communications, PCI_serial, PCI_serial_16550,
133		PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } },
134
135	{ B_PCI_BUS, "Generic 16650 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
136	  { PCI_simple_communications, PCI_serial, PCI_serial_16650,
137		PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } },
138
139	{ B_PCI_BUS, "Generic 16750 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
140	  { PCI_simple_communications, PCI_serial, PCI_serial_16750,
141		PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } },
142
143	{ B_PCI_BUS, "Generic 16850 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
144	  { PCI_simple_communications, PCI_serial, PCI_serial_16850,
145		PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } },
146
147	{ B_PCI_BUS, "Generic 16950 Serial Port", sDefaultRates, NULL, { 8, 8, 8 },
148	  { PCI_simple_communications, PCI_serial, PCI_serial_16950,
149		PCI_INVAL, PCI_INVAL, PCI_INVAL, PCI_INVAL } },
150
151	// non PCI_serial devices
152
153	// beos zz driver supported that one
154	{ B_PCI_BUS, "Lucent Modem", sDefaultRates, NULL, { 8, 8, 8 },
155	  { PCI_simple_communications, PCI_simple_communications_other, 0x00,
156		0x11C1, 0x0480, PCI_INVAL, PCI_INVAL } },
157
158	{ B_PCI_BUS, NULL, NULL, NULL, {0}, {0} }
159};
160
161
162// hardcoded ISA ports
163static struct isa_ports {
164	uint32 ioBase;
165	uint32 irq;
166} sHardcodedPorts[] = {
167	{ 0x3f8, 4 },
168	{ 0x2f8, 3 },
169	{ 0x3e8, 4 },
170	{ 0x2e8, 3 },
171};
172
173#if 0
174status_t
175pc_serial_device_added(pc_device device, void **cookie)
176{
177	TRACE_FUNCALLS("> pc_serial_device_added(0x%08x, 0x%08x)\n", device, cookie);
178
179	status_t status = B_OK;
180	const pc_device_descriptor *descriptor
181		= gUSBModule->get_device_descriptor(device);
182
183	TRACE_ALWAYS("probing device: 0x%04x/0x%04x\n", descriptor->vendor_id,
184		descriptor->product_id);
185
186	*cookie = NULL;
187	SerialDevice *serialDevice = SerialDevice::MakeDevice(device,
188		descriptor->vendor_id, descriptor->product_id);
189
190	const pc_configuration_info *configuration
191		= gUSBModule->get_nth_configuration(device, 0);
192
193	if (!configuration)
194		return B_ERROR;
195
196	status = serialDevice->AddDevice(configuration);
197	if (status < B_OK) {
198		delete serialDevice;
199		return status;
200	}
201
202	acquire_sem(gDriverLock);
203	for (int32 i = 0; i < DEVICES_COUNT; i++) {
204		if (gSerialDevices[i] != NULL)
205			continue;
206
207		status = serialDevice->Init();
208		if (status < B_OK) {
209			delete serialDevice;
210			return status;
211		}
212
213		gSerialDevices[i] = serialDevice;
214		*cookie = serialDevice;
215
216		release_sem(gDriverLock);
217		TRACE_ALWAYS("%s (0x%04x/0x%04x) added\n", serialDevice->Description(),
218			descriptor->vendor_id, descriptor->product_id);
219		return B_OK;
220	}
221
222	release_sem(gDriverLock);
223	return B_ERROR;
224}
225
226
227status_t
228pc_serial_device_removed(void *cookie)
229{
230	TRACE_FUNCALLS("> pc_serial_device_removed(0x%08x)\n", cookie);
231
232	acquire_sem(gDriverLock);
233
234	SerialDevice *device = (SerialDevice *)cookie;
235	for (int32 i = 0; i < DEVICES_COUNT; i++) {
236		if (gSerialDevices[i] == device) {
237			if (device->IsOpen()) {
238				// the device will be deleted upon being freed
239				device->Removed();
240			} else {
241				delete device;
242				gSerialDevices[i] = NULL;
243			}
244			break;
245		}
246	}
247
248	release_sem(gDriverLock);
249	TRACE_FUNCRET("< pc_serial_device_removed() returns\n");
250	return B_OK;
251}
252#endif
253
254//#pragma mark -
255
256status_t
257pc_serial_insert_device(SerialDevice *device)
258{
259	status_t status = B_OK;
260
261	//XXX fix leaks!
262	acquire_sem(gDriverLock);
263	for (int32 i = 0; i < DEVICES_COUNT; i++) {
264		if (gSerialDevices[i] != NULL)
265			continue;
266
267		status = device->Init();
268		if (status < B_OK) {
269			delete device;
270			//return status;
271			break;
272		}
273
274		gSerialDevices[i] = device;
275
276		release_sem(gDriverLock);
277		TRACE_ALWAYS("%s added\n", device->Description());
278		return B_OK;
279	}
280
281	release_sem(gDriverLock);
282	return B_ERROR;
283}
284
285
286// until we support ISA device enumeration from PnP BIOS or ACPI,
287// we have to probe the 4 default COM ports...
288status_t
289scan_isa_hardcoded()
290{
291#ifdef HANDLE_ISA_COM
292	int i;
293	bool serialDebug = get_safemode_boolean("serial_debug_output", true);
294
295	for (i = 0; i < 4; i++) {
296		// skip the port used for kernel debugging...
297		if (serialDebug && sHardcodedPorts[i].ioBase == gKernelDebugPort) {
298			TRACE_ALWAYS("Skipping port %d as it is used for kernel debug.\n", i);
299			continue;
300		}
301
302		SerialDevice *device;
303		device = new(std::nothrow) SerialDevice(&sSupportedDevices[0],
304			sHardcodedPorts[i].ioBase, sHardcodedPorts[i].irq);
305		if (device != NULL && device->Probe())
306			pc_serial_insert_device(device);
307		else
308			delete device;
309	}
310#endif
311	return B_OK;
312}
313
314
315status_t
316scan_pci()
317{
318	pci_info info;
319	int ix;
320	TRACE_ALWAYS("scanning PCI bus (alt)...\n");
321
322	// probe PCI devices
323	for (ix = 0; (*gPCIModule->get_nth_pci_info)(ix, &info) == B_OK; ix++) {
324		// sanity check
325		if ((info.header_type & PCI_header_type_mask) != PCI_header_type_generic)
326			continue;
327		/*
328		TRACE_ALWAYS("probing PCI device %2d [%x|%x|%x] %04x:%04x\n",
329			ix, info.class_base, info.class_sub, info.class_api,
330			info.vendor_id, info.device_id);
331		*/
332
333		const struct serial_support_descriptor *supported = NULL;
334		for (int i = 0; sSupportedDevices[i].name; i++) {
335			if (sSupportedDevices[i].bus != B_PCI_BUS)
336				continue;
337			if (info.class_base != sSupportedDevices[i].match.class_base)
338				continue;
339			if (info.class_sub != sSupportedDevices[i].match.class_sub)
340				continue;
341			if (info.class_api != sSupportedDevices[i].match.class_api)
342				continue;
343			if (sSupportedDevices[i].match.vendor_id != PCI_INVAL
344				&& info.vendor_id != sSupportedDevices[i].match.vendor_id)
345				continue;
346			if (sSupportedDevices[i].match.device_id != PCI_INVAL
347				&& info.device_id != sSupportedDevices[i].match.device_id)
348				continue;
349			supported = &sSupportedDevices[i];
350			break;
351		}
352		if (supported == NULL)
353			continue;
354
355		TRACE_ALWAYS("found PCI device %2d [%x|%x|%x] %04x:%04x as %s\n",
356			ix, info.class_base, info.class_sub, info.class_api,
357			info.vendor_id, info.device_id, supported->name);
358
359		// XXX: interrupt_line doesn't seem to
360		TRACE_ALWAYS("irq line %d, pin %d\n",
361			info.u.h0.interrupt_line, info.u.h0.interrupt_pin);
362		int irq = info.u.h0.interrupt_line;
363
364		SerialDevice *master = NULL;
365
366		uint8 portCount = 0;
367		uint32 maxPorts = DEVICES_COUNT;
368
369		if (supported->constraints.maxports) {
370			maxPorts = supported->constraints.maxports;
371			TRACE_ALWAYS("card supports up to %d ports\n", maxPorts);
372		}
373		if (supported->constraints.subsystem_id_mask) {
374			uint32 id = info.u.h0.subsystem_id;
375			uint32 mask = supported->constraints.subsystem_id_mask;
376			id &= mask;
377			//TRACE_ALWAYS("mask: %lx, masked: %lx\n", mask, id);
378			while (!(mask & 0x1)) {
379				mask >>= 1;
380				id >>= 1;
381			}
382			maxPorts = (uint8)id;
383			TRACE_ALWAYS("subsystem id tells card has %d ports\n", maxPorts);
384		}
385
386		// find I/O ports
387		for (int r = 0; r < 6; r++) {
388			uint32 regbase = info.u.h0.base_registers[r];
389			uint32 reglen = info.u.h0.base_register_sizes[r];
390
391			/**/
392			TRACE("ranges[%d] at 0x%08lx len 0x%lx flags 0x%02x\n", r,
393				regbase, reglen, info.u.h0.base_register_flags[r]);
394			/**/
395
396			// empty
397			if (reglen == 0)
398				continue;
399
400			// not I/O
401			if ((info.u.h0.base_register_flags[r] & PCI_address_space) == 0)
402				continue;
403
404			// the range for sure doesn't contain any UART
405			if (supported->constraints.ignoremask & (1 << r)) {
406				TRACE_ALWAYS("ignored regs at 0x%08lx len 0x%lx\n",
407					regbase, reglen);
408				continue;
409			}
410
411			TRACE_ALWAYS("regs at 0x%08lx len 0x%lx\n",
412				regbase, reglen);
413			//&PCI_address_io_mask
414
415			if (reglen < supported->constraints.minsize)
416				continue;
417			if (reglen > supported->constraints.maxsize)
418				continue;
419
420			SerialDevice *device;
421			uint32 ioport = regbase;
422next_split_alt:
423			// no more to split
424			if ((ioport - regbase) >= reglen)
425				continue;
426
427			if (portCount >= maxPorts)
428				break;
429
430			TRACE_ALWAYS("inserting device at io 0x%04lx as %s\n", ioport,
431				supported->name);
432
433
434/**/
435			device = new(std::nothrow) SerialDevice(supported, ioport, irq, master);
436			if (device == NULL) {
437				TRACE_ALWAYS("can't allocate device\n");
438				continue;
439			}
440
441			if (pc_serial_insert_device(device) < B_OK) {
442				TRACE_ALWAYS("can't insert device\n");
443				continue;
444			}
445/**/			if (master == NULL)
446				master = device;
447
448			ioport += supported->constraints.split;
449			portCount++;
450			goto next_split_alt;
451			// try next part of the I/O range now
452
453		}
454	}
455
456	return B_OK;
457}
458
459
460static void
461check_kernel_debug_port()
462{
463	void *handle;
464	long int value;
465
466	handle = load_driver_settings("kernel");
467	if (handle == NULL)
468		return;
469
470	const char *str = get_driver_parameter(handle, "serial_debug_port",
471		NULL, NULL);
472	if (str != NULL) {
473		value = strtol(str, NULL, 0);
474		if (value >= 4) // XXX: actually should be MAX_SERIAL_PORTS...
475			gKernelDebugPort = (uint32)value;
476		else if (value >= 0) // XXX: we should use the kernel_arg's table...
477			gKernelDebugPort = sHardcodedPorts[value].ioBase;
478	}
479
480	/* TODO: actually handle this in the kernel debugger too!
481	bool enabled = get_driver_boolean_parameter(handle, "serial_debug_output",
482		false, true);
483	if (!enabled)
484		gKernelDebugPort = 0;
485	*/
486
487	unload_driver_settings(handle);
488}
489
490
491//#pragma mark -
492
493
494/* init_hardware - called once the first time the driver is loaded */
495status_t
496init_hardware()
497{
498	TRACE("init_hardware\n");
499	return B_OK;
500}
501
502
503/* init_driver - called every time the driver is loaded. */
504status_t
505init_driver()
506{
507	status_t status;
508	load_settings();
509	create_log_file();
510
511	TRACE_FUNCALLS("> init_driver()\n");
512
513	status = get_module(B_DPC_MODULE_NAME, (module_info **)&gDPCModule);
514	if (status < B_OK)
515		goto err_dpc;
516
517	status = get_module(B_TTY_MODULE_NAME, (module_info **)&gTTYModule);
518	if (status < B_OK)
519		goto err_tty;
520
521	status = get_module(B_PCI_MODULE_NAME, (module_info **)&gPCIModule);
522	if (status < B_OK)
523		goto err_pci;
524
525	status = get_module(B_ISA_MODULE_NAME, (module_info **)&gISAModule);
526	if (status < B_OK)
527		goto err_isa;
528
529	status = gDPCModule->new_dpc_queue(&gDPCHandle, "pc_serial irq",
530		B_REAL_TIME_PRIORITY);
531	if (status != B_OK)
532		goto err_dpcq;
533
534	for (int32 i = 0; i < DEVICES_COUNT; i++)
535		gSerialDevices[i] = NULL;
536
537	gDeviceNames[0] = NULL;
538
539	gDriverLock = create_sem(1, DRIVER_NAME"_devices_table_lock");
540	if (gDriverLock < B_OK) {
541		status = gDriverLock;
542		goto err_sem;
543	}
544
545	status = ENOENT;
546
547	check_kernel_debug_port();
548
549	scan_isa_hardcoded();
550	scan_pci();
551
552	// XXX: ISA cards
553	// XXX: pcmcia
554
555	TRACE_FUNCRET("< init_driver() returns\n");
556	return B_OK;
557
558//err_none:
559	delete_sem(gDriverLock);
560err_sem:
561	gDPCModule->delete_dpc_queue(gDPCHandle);
562	gDPCHandle = NULL;
563err_dpcq:
564	put_module(B_ISA_MODULE_NAME);
565err_isa:
566	put_module(B_PCI_MODULE_NAME);
567err_pci:
568	put_module(B_TTY_MODULE_NAME);
569err_tty:
570	put_module(B_DPC_MODULE_NAME);
571err_dpc:
572	TRACE_FUNCRET("< init_driver() returns %s\n", strerror(status));
573	return status;
574}
575
576
577/* uninit_driver - called every time the driver is unloaded */
578void
579uninit_driver()
580{
581	TRACE_FUNCALLS("> uninit_driver()\n");
582
583	//gUSBModule->uninstall_notify(DRIVER_NAME);
584	acquire_sem(gDriverLock);
585
586	for (int32 i = 0; i < DEVICES_COUNT; i++) {
587		if (gSerialDevices[i]) {
588			/*
589			if (gSerialDevices[i]->Master() == gSerialDevices[i])
590				remove_io_interrupt_handler(gSerialDevices[i]->IRQ(),
591					pc_serial_interrupt, gSerialDevices[i]);
592			*/
593			delete gSerialDevices[i];
594			gSerialDevices[i] = NULL;
595		}
596	}
597
598	for (int32 i = 0; gDeviceNames[i]; i++)
599		free(gDeviceNames[i]);
600
601	delete_sem(gDriverLock);
602	gDPCModule->delete_dpc_queue(gDPCHandle);
603	gDPCHandle = NULL;
604	put_module(B_ISA_MODULE_NAME);
605	put_module(B_PCI_MODULE_NAME);
606	put_module(B_TTY_MODULE_NAME);
607	put_module(B_DPC_MODULE_NAME);
608
609	TRACE_FUNCRET("< uninit_driver() returns\n");
610}
611
612
613bool
614pc_serial_service(struct tty *tty, uint32 op, void *buffer, size_t length)
615{
616	TRACE_FUNCALLS("> pc_serial_service(%p, 0x%08lx, %p, %lu)\n", tty,
617		op, buffer, length);
618
619
620	for (int32 i = 0; i < DEVICES_COUNT; i++) {
621		if (gSerialDevices[i]
622			&& gSerialDevices[i]->Service(tty, op, buffer, length)) {
623			TRACE_FUNCRET("< pc_serial_service() returns: true\n");
624			return true;
625		}
626	}
627
628	TRACE_FUNCRET("< pc_serial_service() returns: false\n");
629	return false;
630}
631
632
633static void
634pc_serial_dpc(void *arg)
635{
636	SerialDevice *master = (SerialDevice *)arg;
637	TRACE_FUNCALLS("> pc_serial_dpc(%p)\n", arg);
638	master->InterruptHandler();
639}
640
641
642int32
643pc_serial_interrupt(void *arg)
644{
645	SerialDevice *device = (SerialDevice *)arg;
646	TRACE_FUNCALLS("> pc_serial_interrupt(%p)\n", arg);
647
648	if (!device)
649		return B_UNHANDLED_INTERRUPT;
650
651	if (device->IsInterruptPending()) {
652		status_t err;
653		err = gDPCModule->queue_dpc(gDPCHandle, pc_serial_dpc, device);
654		if (err != B_OK)
655			dprintf(DRIVER_NAME ": error queing irq: %s\n", strerror(err));
656		else {
657			TRACE_FUNCRET("< pc_serial_interrupt() returns: resched\n");
658			return B_INVOKE_SCHEDULER;
659		}
660	}
661
662	TRACE_FUNCRET("< pc_serial_interrupt() returns: unhandled\n");
663	return B_UNHANDLED_INTERRUPT;
664}
665
666
667/* pc_serial_open - handle open() calls */
668status_t
669pc_serial_open(const char *name, uint32 flags, void **cookie)
670{
671	TRACE_FUNCALLS("> pc_serial_open(%s, 0x%08x, 0x%08x)\n", name, flags, cookie);
672	acquire_sem(gDriverLock);
673	status_t status = ENODEV;
674
675	*cookie = NULL;
676	int i = strtol(name + strlen(sDeviceBaseName), NULL, 10);
677	if (i >= 0 && i < DEVICES_COUNT && gSerialDevices[i]) {
678		status = gSerialDevices[i]->Open(flags);
679		*cookie = gSerialDevices[i];
680	}
681
682	release_sem(gDriverLock);
683	TRACE_FUNCRET("< pc_serial_open() returns: 0x%08x\n", status);
684	return status;
685}
686
687
688/* pc_serial_read - handle read() calls */
689status_t
690pc_serial_read(void *cookie, off_t position, void *buffer, size_t *numBytes)
691{
692	TRACE_FUNCALLS("> pc_serial_read(0x%08x, %lld, 0x%08x, %d)\n", cookie,
693		position, buffer, *numBytes);
694	SerialDevice *device = (SerialDevice *)cookie;
695	return device->Read((char *)buffer, numBytes);
696}
697
698
699/* pc_serial_write - handle write() calls */
700status_t
701pc_serial_write(void *cookie, off_t position, const void *buffer,
702	size_t *numBytes)
703{
704	TRACE_FUNCALLS("> pc_serial_write(0x%08x, %lld, 0x%08x, %d)\n", cookie,
705		position, buffer, *numBytes);
706	SerialDevice *device = (SerialDevice *)cookie;
707	return device->Write((const char *)buffer, numBytes);
708}
709
710
711/* pc_serial_control - handle ioctl calls */
712status_t
713pc_serial_control(void *cookie, uint32 op, void *arg, size_t length)
714{
715	TRACE_FUNCALLS("> pc_serial_control(0x%08x, 0x%08x, 0x%08x, %d)\n",
716		cookie, op, arg, length);
717	SerialDevice *device = (SerialDevice *)cookie;
718	return device->Control(op, arg, length);
719}
720
721
722/* pc_serial_select - handle select start */
723status_t
724pc_serial_select(void *cookie, uint8 event, uint32 ref, selectsync *sync)
725{
726	TRACE_FUNCALLS("> pc_serial_select(0x%08x, 0x%08x, 0x%08x, %p)\n",
727		cookie, event, ref, sync);
728	SerialDevice *device = (SerialDevice *)cookie;
729	return device->Select(event, ref, sync);
730}
731
732
733/* pc_serial_deselect - handle select exit */
734status_t
735pc_serial_deselect(void *cookie, uint8 event, selectsync *sync)
736{
737	TRACE_FUNCALLS("> pc_serial_deselect(0x%08x, 0x%08x, %p)\n",
738		cookie, event, sync);
739	SerialDevice *device = (SerialDevice *)cookie;
740	return device->DeSelect(event, sync);
741}
742
743
744/* pc_serial_close - handle close() calls */
745status_t
746pc_serial_close(void *cookie)
747{
748	TRACE_FUNCALLS("> pc_serial_close(0x%08x)\n", cookie);
749	SerialDevice *device = (SerialDevice *)cookie;
750	return device->Close();
751}
752
753
754/* pc_serial_free - called after last device is closed, and all i/o complete. */
755status_t
756pc_serial_free(void *cookie)
757{
758	TRACE_FUNCALLS("> pc_serial_free(0x%08x)\n", cookie);
759	SerialDevice *device = (SerialDevice *)cookie;
760	acquire_sem(gDriverLock);
761	status_t status = device->Free();
762	if (device->IsRemoved()) {
763		for (int32 i = 0; i < DEVICES_COUNT; i++) {
764			if (gSerialDevices[i] == device) {
765				// the device is removed already but as it was open the
766				// removed hook has not deleted the object
767				delete device;
768				gSerialDevices[i] = NULL;
769				break;
770			}
771		}
772	}
773
774	release_sem(gDriverLock);
775	return status;
776}
777
778
779/* publish_devices - null-terminated array of devices supported by this driver. */
780const char **
781publish_devices()
782{
783	TRACE_FUNCALLS("> publish_devices()\n");
784	for (int32 i = 0; gDeviceNames[i]; i++)
785		free(gDeviceNames[i]);
786
787	int j = 0;
788	acquire_sem(gDriverLock);
789	for(int i = 0; i < DEVICES_COUNT; i++) {
790		if (gSerialDevices[i]) {
791			gDeviceNames[j] = (char *)malloc(strlen(sDeviceBaseName) + 4);
792			if (gDeviceNames[j]) {
793				sprintf(gDeviceNames[j], "%s%d", sDeviceBaseName, i);
794				j++;
795			} else
796				TRACE_ALWAYS("publish_devices - no memory to allocate device names\n");
797		}
798	}
799
800	gDeviceNames[j] = NULL;
801	release_sem(gDriverLock);
802	return (const char **)&gDeviceNames[0];
803}
804
805
806/* find_device - return poiter to device hooks structure for a given device */
807device_hooks *
808find_device(const char *name)
809{
810	static device_hooks deviceHooks = {
811		pc_serial_open,			/* -> open entry point */
812		pc_serial_close,			/* -> close entry point */
813		pc_serial_free,			/* -> free cookie */
814		pc_serial_control,			/* -> control entry point */
815		pc_serial_read,			/* -> read entry point */
816		pc_serial_write,			/* -> write entry point */
817		pc_serial_select,			/* -> select entry point */
818		pc_serial_deselect			/* -> deselect entry point */
819	};
820
821	TRACE_FUNCALLS("> find_device(%s)\n", name);
822	return &deviceHooks;
823}
824