VirtualBox

source: vbox/trunk/src/VBox/Devices/Parallel/DrvHostParallel.cpp@ 39684

Last change on this file since 39684 was 39684, checked in by vboxsync, 13 years ago

Parallel: Some love for the long abandoned parallel port emulation. General cleanup and implemented passthrough of EPP transfers

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.5 KB
Line 
1/* $Id: DrvHostParallel.cpp 39684 2011-12-29 17:50:00Z vboxsync $ */
2/** @file
3 * VirtualBox Host Parallel Port Driver.
4 *
5 * Contributed by: Alexander Eichner
6 */
7
8/*
9 * Copyright (C) 2006-2010 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20/*******************************************************************************
21* Header Files *
22*******************************************************************************/
23#define LOG_GROUP LOG_GROUP_DRV_HOST_PARALLEL
24#include <VBox/vmm/pdmdrv.h>
25#include <VBox/vmm/pdmthread.h>
26#include <iprt/asm.h>
27#include <iprt/assert.h>
28#include <iprt/file.h>
29#include <iprt/pipe.h>
30#include <iprt/semaphore.h>
31#include <iprt/stream.h>
32#include <iprt/uuid.h>
33
34#ifdef RT_OS_LINUX
35# include <sys/ioctl.h>
36# include <sys/types.h>
37# include <sys/stat.h>
38# include <sys/poll.h>
39# include <fcntl.h>
40# include <unistd.h>
41# include <linux/ppdev.h>
42# include <linux/parport.h>
43# include <errno.h>
44#endif
45
46#include "VBoxDD.h"
47
48
49/*******************************************************************************
50* Structures and Typedefs *
51*******************************************************************************/
52/**
53 * Host parallel port driver instance data.
54 * @implements PDMIHOSTPARALLELCONNECTOR
55 */
56typedef struct DRVHOSTPARALLEL
57{
58 /** Pointer to the driver instance structure. */
59 PPDMDRVINS pDrvIns;
60 /** Pointer to the char port interface of the driver/device above us. */
61 PPDMIHOSTPARALLELPORT pDrvHostParallelPort;
62 /** Our host device interface. */
63 PDMIHOSTPARALLELCONNECTOR IHostParallelConnector;
64 /** Device Path */
65 char *pszDevicePath;
66 /** Device Handle */
67 RTFILE hFileDevice;
68 /** Thread waiting for interrupts. */
69 PPDMTHREAD pMonitorThread;
70 /** Wakeup pipe read end. */
71 RTPIPE hWakeupPipeR;
72 /** Wakeup pipe write end. */
73 RTPIPE hWakeupPipeW;
74 /** Current mode the parallel port is in. */
75 PDMPARALLELPORTMODE enmModeCur;
76} DRVHOSTPARALLEL, *PDRVHOSTPARALLEL;
77
78/** Converts a pointer to DRVHOSTPARALLEL::IHostDeviceConnector to a PDRHOSTPARALLEL. */
79#define PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface) ( (PDRVHOSTPARALLEL)((uintptr_t)pInterface - RT_OFFSETOF(DRVHOSTPARALLEL, IHostParallelConnector)) )
80
81/**
82 * Changes the current mode of the host parallel port.
83 *
84 * @returns VBox status code.
85 * @param pThis The host parallel port instance data.
86 * @param enmMode The mode to change the port to.
87 */
88static int drvHostParallelSetMode(PDRVHOSTPARALLEL pThis, PDMPARALLELPORTMODE enmMode)
89{
90 int iMode = 0;
91 int rc = VINF_SUCCESS;
92 int rcLnx;
93
94 LogFlow(("%s: mode=%d\n", __FUNCTION__, enmMode));
95
96 if (pThis->enmModeCur != enmMode)
97 {
98 switch (enmMode)
99 {
100 case PDM_PARALLEL_PORT_MODE_SPP:
101 iMode = IEEE1284_MODE_COMPAT;
102 break;
103 case PDM_PARALLEL_PORT_MODE_EPP_DATA:
104 iMode = IEEE1284_MODE_EPP | IEEE1284_DATA;
105 break;
106 case PDM_PARALLEL_PORT_MODE_EPP_ADDR:
107 iMode = IEEE1284_MODE_EPP | IEEE1284_ADDR;
108 break;
109 case PDM_PARALLEL_PORT_MODE_ECP:
110 case PDM_PARALLEL_PORT_MODE_INVALID:
111 default:
112 return VERR_NOT_SUPPORTED;
113 }
114
115 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPSETMODE, &iMode);
116 if (RT_UNLIKELY(rcLnx < 0))
117 rc = RTErrConvertFromErrno(errno);
118 else
119 pThis->enmModeCur = enmMode;
120 }
121
122 return rc;
123}
124
125/* -=-=-=-=- IBase -=-=-=-=- */
126
127/**
128 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
129 */
130static DECLCALLBACK(void *) drvHostParallelQueryInterface(PPDMIBASE pInterface, const char *pszIID)
131{
132 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
133 PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL);
134
135 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
136 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTPARALLELCONNECTOR, &pThis->IHostParallelConnector);
137 return NULL;
138}
139
140/* -=-=-=-=- IHostDeviceConnector -=-=-=-=- */
141
142/** @copydoc PDMICHARCONNECTOR::pfnWrite */
143static DECLCALLBACK(int) drvHostParallelWrite(PPDMIHOSTPARALLELCONNECTOR pInterface, const void *pvBuf, size_t cbWrite, PDMPARALLELPORTMODE enmMode)
144{
145 PDRVHOSTPARALLEL pThis = PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface);
146 int rc = VINF_SUCCESS;
147 int rcLnx = 0;
148
149 LogFlow(("%s: pvBuf=%#p cbWrite=%d\n", __FUNCTION__, pvBuf, cbWrite));
150
151 rc = drvHostParallelSetMode(pThis, enmMode);
152 if (RT_FAILURE(rc))
153 return rc;
154
155 if (enmMode == PDM_PARALLEL_PORT_MODE_SPP)
156 {
157 /* Set the data lines directly. */
158 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPWDATA, pvBuf);
159 }
160 else
161 {
162 /* Use write interface. */
163 rcLnx = write(RTFileToNative(pThis->hFileDevice), pvBuf, cbWrite);
164 }
165 if (RT_UNLIKELY(rcLnx < 0))
166 rc = RTErrConvertFromErrno(errno);
167
168 return rc;
169}
170
171static DECLCALLBACK(int) drvHostParallelRead(PPDMIHOSTPARALLELCONNECTOR pInterface, void *pvBuf, size_t cbRead, PDMPARALLELPORTMODE enmMode)
172{
173 PDRVHOSTPARALLEL pThis = PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface);
174 int rc = VINF_SUCCESS;
175 int rcLnx = 0;
176
177 LogFlow(("%s: pvBuf=%#p cbRead=%d\n", __FUNCTION__, pvBuf, cbRead));
178
179 rc = drvHostParallelSetMode(pThis, enmMode);
180 if (RT_FAILURE(rc))
181 return rc;
182
183 if (enmMode == PDM_PARALLEL_PORT_MODE_SPP)
184 {
185 /* Set the data lines directly. */
186 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPWDATA, pvBuf);
187 }
188 else
189 {
190 /* Use write interface. */
191 rcLnx = read(RTFileToNative(pThis->hFileDevice), pvBuf, cbRead);
192 }
193 if (RT_UNLIKELY(rcLnx < 0))
194 rc = RTErrConvertFromErrno(errno);
195
196 return rc;
197}
198
199static DECLCALLBACK(int) drvHostParallelSetPortDirection(PPDMIHOSTPARALLELCONNECTOR pInterface, bool fForward)
200{
201 PDRVHOSTPARALLEL pThis = PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface);
202 int rc = VINF_SUCCESS;
203 int rcLnx = 0;
204 int iMode = 0;
205
206 if (!fForward)
207 iMode = 1;
208
209 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPDATADIR, &iMode);
210 if (RT_UNLIKELY(rcLnx < 0))
211 rc = RTErrConvertFromErrno(errno);
212
213 return rc;
214}
215
216static DECLCALLBACK(int) drvHostParallelWriteControl(PPDMIHOSTPARALLELCONNECTOR pInterface, uint8_t fReg)
217{
218 PDRVHOSTPARALLEL pThis = PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface);
219 int rc = VINF_SUCCESS;
220 int rcLnx = 0;
221
222 LogFlow(("%s: fReg=%d\n", __FUNCTION__, fReg));
223 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPWCONTROL, &fReg);
224 if (RT_UNLIKELY(rcLnx < 0))
225 rc = RTErrConvertFromErrno(errno);
226
227 return rc;
228}
229
230static DECLCALLBACK(int) drvHostParallelReadControl(PPDMIHOSTPARALLELCONNECTOR pInterface, uint8_t *pfReg)
231{
232 PDRVHOSTPARALLEL pThis = PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface);
233 int rc = VINF_SUCCESS;
234 int rcLnx = 0;
235 uint8_t fReg = 0;
236
237 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPRCONTROL, &fReg);
238 if (RT_UNLIKELY(rcLnx < 0))
239 rc = RTErrConvertFromErrno(errno);
240 else
241 {
242 LogFlow(("%s: fReg=%d\n", __FUNCTION__, fReg));
243 *pfReg = fReg;
244 }
245
246 return rc;
247}
248
249static DECLCALLBACK(int) drvHostParallelReadStatus(PPDMIHOSTPARALLELCONNECTOR pInterface, uint8_t *pfReg)
250{
251 PDRVHOSTPARALLEL pThis = PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface);
252 int rc = VINF_SUCCESS;
253 int rcLnx = 0;
254 uint8_t fReg = 0;
255
256 rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPRSTATUS, &fReg);
257 if (RT_UNLIKELY(rcLnx < 0))
258 rc = RTErrConvertFromErrno(errno);
259 else
260 {
261 LogFlow(("%s: fReg=%d\n", __FUNCTION__, fReg));
262 *pfReg = fReg;
263 }
264
265 return rc;
266}
267
268static DECLCALLBACK(int) drvHostParallelMonitorThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
269{
270 PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL);
271 struct pollfd aFDs[2];
272
273 /*
274 * We can wait for interrupts using poll on linux hosts.
275 */
276 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
277 {
278 int rc;
279
280 aFDs[0].fd = RTFileToNative(pThis->hFileDevice);
281 aFDs[0].events = POLLIN;
282 aFDs[0].revents = 0;
283 aFDs[1].fd = RTPipeToNative(pThis->hWakeupPipeR);
284 aFDs[1].events = POLLIN | POLLERR | POLLHUP;
285 aFDs[1].revents = 0;
286 rc = poll(aFDs, RT_ELEMENTS(aFDs), -1);
287 if (rc < 0)
288 {
289 AssertMsgFailed(("poll failed with rc=%d\n", RTErrConvertFromErrno(errno)));
290 return RTErrConvertFromErrno(errno);
291 }
292
293 if (pThread->enmState != PDMTHREADSTATE_RUNNING)
294 break;
295 if (rc > 0 && aFDs[1].revents)
296 {
297 if (aFDs[1].revents & (POLLHUP | POLLERR | POLLNVAL))
298 break;
299 /* notification to terminate -- drain the pipe */
300 char ch;
301 size_t cbRead;
302 RTPipeRead(pThis->hWakeupPipeR, &ch, 1, &cbRead);
303 continue;
304 }
305
306 /* Interrupt occurred. */
307 rc = pThis->pDrvHostParallelPort->pfnNotifyInterrupt(pThis->pDrvHostParallelPort);
308 AssertRC(rc);
309 }
310
311 return VINF_SUCCESS;
312}
313
314/**
315 * Unblock the monitor thread so it can respond to a state change.
316 *
317 * @returns a VBox status code.
318 * @param pDrvIns The driver instance.
319 * @param pThread The send thread.
320 */
321static DECLCALLBACK(int) drvHostParallelWakeupMonitorThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
322{
323 PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL);
324 size_t cbIgnored;
325 return RTPipeWrite(pThis->hWakeupPipeW, "", 1, &cbIgnored);
326}
327
328/**
329 * Destruct a host parallel driver instance.
330 *
331 * Most VM resources are freed by the VM. This callback is provided so that
332 * any non-VM resources can be freed correctly.
333 *
334 * @param pDrvIns The driver instance data.
335 */
336static DECLCALLBACK(void) drvHostParallelDestruct(PPDMDRVINS pDrvIns)
337{
338 PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL);
339 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
340 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
341 int rc;
342
343 if (pThis->hFileDevice != NIL_RTFILE)
344 ioctl(RTFileToNative(pThis->hFileDevice), PPRELEASE);
345
346 rc = RTPipeClose(pThis->hWakeupPipeW); AssertRC(rc);
347 pThis->hWakeupPipeW = NIL_RTPIPE;
348
349 rc = RTPipeClose(pThis->hWakeupPipeR); AssertRC(rc);
350 pThis->hWakeupPipeR = NIL_RTPIPE;
351
352 rc = RTFileClose(pThis->hFileDevice); AssertRC(rc);
353 pThis->hFileDevice = NIL_RTFILE;
354
355 if (pThis->pszDevicePath)
356 {
357 MMR3HeapFree(pThis->pszDevicePath);
358 pThis->pszDevicePath = NULL;
359 }
360}
361
362/**
363 * Construct a host parallel driver instance.
364 *
365 * @copydoc FNPDMDRVCONSTRUCT
366 */
367static DECLCALLBACK(int) drvHostParallelConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
368{
369 PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL);
370 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
371 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
372
373 /*
374 * Init basic data members and interfaces.
375 *
376 * Must be done before returning any failure because we've got a destructor.
377 */
378 pThis->hFileDevice = NIL_RTFILE;
379 pThis->hWakeupPipeR = NIL_RTPIPE;
380 pThis->hWakeupPipeW = NIL_RTPIPE;
381
382 /* IBase. */
383 pDrvIns->IBase.pfnQueryInterface = drvHostParallelQueryInterface;
384 /* IHostParallelConnector. */
385 pThis->IHostParallelConnector.pfnWrite = drvHostParallelWrite;
386 pThis->IHostParallelConnector.pfnRead = drvHostParallelRead;
387 pThis->IHostParallelConnector.pfnSetPortDirection = drvHostParallelSetPortDirection;
388 pThis->IHostParallelConnector.pfnWriteControl = drvHostParallelWriteControl;
389 pThis->IHostParallelConnector.pfnReadControl = drvHostParallelReadControl;
390 pThis->IHostParallelConnector.pfnReadStatus = drvHostParallelReadStatus;
391
392 /*
393 * Validate the config.
394 */
395 if (!CFGMR3AreValuesValid(pCfg, "DevicePath\0"))
396 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
397 N_("Unknown host parallel configuration option, only supports DevicePath"));
398
399 /*
400 * Query configuration.
401 */
402 /* Device */
403 int rc = CFGMR3QueryStringAlloc(pCfg, "DevicePath", &pThis->pszDevicePath);
404 if (RT_FAILURE(rc))
405 {
406 AssertMsgFailed(("Configuration error: query for \"DevicePath\" string returned %Rra.\n", rc));
407 return rc;
408 }
409
410 /*
411 * Open the device
412 */
413 rc = RTFileOpen(&pThis->hFileDevice, pThis->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
414 if (RT_FAILURE(rc))
415 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Parallel#%d could not open '%s'"),
416 pDrvIns->iInstance, pThis->pszDevicePath);
417
418 /*
419 * Try to get exclusive access to parallel port
420 */
421 rc = ioctl(RTFileToNative(pThis->hFileDevice), PPEXCL);
422 if (rc < 0)
423 return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
424 N_("Parallel#%d could not get exclusive access for parallel port '%s'"
425 "Be sure that no other process or driver accesses this port"),
426 pDrvIns->iInstance, pThis->pszDevicePath);
427
428 /*
429 * Claim the parallel port
430 */
431 rc = ioctl(RTFileToNative(pThis->hFileDevice), PPCLAIM);
432 if (rc < 0)
433 return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
434 N_("Parallel#%d could not claim parallel port '%s'"
435 "Be sure that no other process or driver accesses this port"),
436 pDrvIns->iInstance, pThis->pszDevicePath);
437
438 /*
439 * Get the IHostParallelPort interface of the above driver/device.
440 */
441 pThis->pDrvHostParallelPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTPARALLELPORT);
442 if (!pThis->pDrvHostParallelPort)
443 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, N_("Parallel#%d has no parallel port interface above"),
444 pDrvIns->iInstance);
445
446 /*
447 * Create wakeup pipe.
448 */
449 rc = RTPipeCreate(&pThis->hWakeupPipeR, &pThis->hWakeupPipeW, 0 /*fFlags*/);
450 AssertRCReturn(rc, rc);
451
452 /*
453 * Start in SPP mode.
454 */
455 pThis->enmModeCur = PDM_PARALLEL_PORT_MODE_INVALID;
456 rc = drvHostParallelSetMode(pThis, PDM_PARALLEL_PORT_MODE_SPP);
457 if (RT_FAILURE(rc))
458 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostParallel#%d cannot change mode of parallel mode to SPP"), pDrvIns->iInstance);
459
460 /*
461 * Start waiting for interrupts.
462 */
463 rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pMonitorThread, pThis, drvHostParallelMonitorThread, drvHostParallelWakeupMonitorThread, 0,
464 RTTHREADTYPE_IO, "ParMon");
465 if (RT_FAILURE(rc))
466 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostParallel#%d cannot create monitor thread"), pDrvIns->iInstance);
467
468
469 return VINF_SUCCESS;
470}
471
472/**
473 * Char driver registration record.
474 */
475const PDMDRVREG g_DrvHostParallel =
476{
477 /* u32Version */
478 PDM_DRVREG_VERSION,
479 /* szName */
480 "HostParallel",
481 /* szRCMod */
482 "",
483 /* szR0Mod */
484 "",
485 /* pszDescription */
486 "Parallel host driver.",
487 /* fFlags */
488 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
489 /* fClass. */
490 PDM_DRVREG_CLASS_CHAR,
491 /* cMaxInstances */
492 ~0,
493 /* cbInstance */
494 sizeof(DRVHOSTPARALLEL),
495 /* pfnConstruct */
496 drvHostParallelConstruct,
497 /* pfnDestruct */
498 drvHostParallelDestruct,
499 /* pfnRelocate */
500 NULL,
501 /* pfnIOCtl */
502 NULL,
503 /* pfnPowerOn */
504 NULL,
505 /* pfnReset */
506 NULL,
507 /* pfnSuspend */
508 NULL,
509 /* pfnResume */
510 NULL,
511 /* pfnAttach */
512 NULL,
513 /* pfnDetach */
514 NULL,
515 /* pfnPowerOff */
516 NULL,
517 /* pfnSoftReset */
518 NULL,
519 /* u32EndVersion */
520 PDM_DRVREG_VERSION
521};
522
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette