/* Event rate throttling timer to emulate the auxiliary device sampling rate. */ static DECLCALLBACK(void) ps2mThrottleTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { RT_NOREF2(pDevIns, pTimer); PPS2M pThis = (PS2M *)pvUser; uint32_t uHaveEvents; /* Grab the lock to avoid races with PutEvent(). */ int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY); AssertReleaseRC(rc); #if 0 /* If the input queue is not empty, restart the timer. */ #else /* If more movement is accumulated, report it and restart the timer. */ uHaveEvents = pThis->iAccumX | pThis->iAccumY | pThis->iAccumZ | (pThis->fCurrB != pThis->fReportedB); LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no")); if (uHaveEvents) #endif { /* Report accumulated data, poke the KBC, and start the timer. */ ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->evtQ, true); KBCUpdateInterrupts(pThis->pParent); TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay); } else pThis->fThrottleActive = false; PDMCritSectLeave(pThis->pCritSectR3); }
/* Event rate throttling timer to emulate the auxiliary device sampling rate. */ static DECLCALLBACK(void) ps2mThrottleTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { PPS2M pThis = (PS2M *)pvUser; NOREF(pDevIns); uint32_t uHaveEvents; /* Grab the lock to avoid races with PutEvent(). */ int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY); AssertReleaseRC(rc); #if 0 /* If the input queue is not empty, restart the timer. */ #else /* If more movement is accumulated, report it and restart the timer. */ uHaveEvents = pThis->iAccumX | pThis->iAccumY | pThis->iAccumZ | pThis->fAccumB; LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no")); if (uHaveEvents) #endif { ps2mReportAccumulatedEvents(pThis); TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay); } else pThis->fThrottleActive = false; PDMCritSectLeave(pThis->pCritSectR3); }
/** * Mouse event handler. * * @returns VBox status code. * @param pThis The PS/2 auxiliary device instance data. * @param dx X direction movement delta. * @param dy Y direction movement delta. * @param dz Z (vertical scroll) movement delta. * @param dw W (horizontal scroll) movement delta. * @param fButtons Depressed button mask. */ static int ps2mPutEventWorker(PPS2M pThis, int32_t dx, int32_t dy, int32_t dz, int32_t dw, uint32_t fButtons) { RT_NOREF1(dw); int rc = VINF_SUCCESS; /* Update internal accumulators and button state. */ pThis->iAccumX += dx; pThis->iAccumY += dy; pThis->iAccumZ += dz; pThis->fAccumB |= fButtons; /// @todo accumulate based on current protocol? pThis->fCurrB = fButtons; #if 1 /* Report the event and start the throttle timer unless it's already running. */ if (!pThis->fThrottleActive) { ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->evtQ, true); KBCUpdateInterrupts(pThis->pParent); pThis->fThrottleActive = true; TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay); } #else /* Clamp the delta values to the allowed range. */ dx = RT_MIN(RT_MAX(dx, -256), 255); dy = RT_MIN(RT_MAX(dy, -256), 255); /* Start with the sync bit. */ val = RT_BIT(3); /* Add buttons 1-3. */ val |= fButtons & PS2M_STD_BTN_MASK; /* Set the X/Y sign bits. */ if (dx < 0) val |= RT_BIT(4); if (dy < 0) val |= RT_BIT(5); ps2kInsertQueue((GeneriQ *)&pThis->evtQ, val); ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dx); ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dy); if (pThis->enmProtocol > PS2M_PROTO_PS2STD) { ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dz); } #endif return rc; }
void PS2MSaveState(PPS2M pThis, PSSMHANDLE pSSM) { LogFlowFunc(("Saving PS2M state\n")); /* Save the core auxiliary device state. */ SSMR3PutU8(pSSM, pThis->u8State); SSMR3PutU8(pSSM, pThis->u8SampleRate); SSMR3PutU8(pSSM, pThis->u8Resolution); SSMR3PutU8(pSSM, pThis->u8CurrCmd); SSMR3PutU8(pSSM, pThis->enmMode); SSMR3PutU8(pSSM, pThis->enmProtocol); SSMR3PutU8(pSSM, pThis->enmKnockState); /* Save the command and event queues. */ ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->cmdQ); ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->evtQ); /* Save the command delay timer. Note that the rate throttling * timer is *not* saved. */ TMR3TimerSave(pThis->CTX_SUFF(pDelayTimer), pSSM); }
int PS2MLoadState(PPS2M pThis, PSSMHANDLE pSSM, uint32_t uVersion) { uint8_t u8; int rc; NOREF(uVersion); LogFlowFunc(("Loading PS2M state version %u\n", uVersion)); /* Load the basic auxiliary device state. */ SSMR3GetU8(pSSM, &pThis->u8State); SSMR3GetU8(pSSM, &pThis->u8SampleRate); SSMR3GetU8(pSSM, &pThis->u8Resolution); SSMR3GetU8(pSSM, &pThis->u8CurrCmd); SSMR3GetU8(pSSM, &u8); pThis->enmMode = (PS2M_MODE)u8; SSMR3GetU8(pSSM, &u8); pThis->enmProtocol = (PS2M_PROTO)u8; SSMR3GetU8(pSSM, &u8); pThis->enmKnockState = (PS2M_KNOCK_STATE)u8; /* Load the command and event queues. */ rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->cmdQ); AssertRCReturn(rc, rc); rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->evtQ); AssertRCReturn(rc, rc); /* Load the command delay timer, just in case. */ rc = TMR3TimerLoad(pThis->CTX_SUFF(pDelayTimer), pSSM); AssertRCReturn(rc, rc); /* Recalculate the throttling delay. */ ps2mSetRate(pThis, pThis->u8SampleRate); //@todo: Is this the right place/logic? ps2mSetDriverState(pThis, !!(pThis->u8State & AUX_STATE_ENABLED)); return rc; }
/** * Receive and process a byte sent by the keyboard controller. * * @param pThis The PS/2 auxiliary device instance data. * @param cmd The command (or data) byte. */ int PS2MByteToAux(PPS2M pThis, uint8_t cmd) { uint8_t u8Val; bool fHandled = true; LogFlowFunc(("cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd)); //LogRel(("aux: cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd)); if (pThis->enmMode == AUX_MODE_RESET) { /* In reset mode, do not respond at all. */ return VINF_SUCCESS; } else if (pThis->enmMode == AUX_MODE_WRAP) { /* In wrap mode, bounce most data right back.*/ if (cmd == ACMD_RESET || cmd == ACMD_RESET_WRAP) ; /* Handle as regular commands. */ else { ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, cmd); return VINF_SUCCESS; } } switch (cmd) { case ACMD_SET_SCALE_11: pThis->u8State &= ~AUX_STATE_SCALING; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_SCALE_21: pThis->u8State |= AUX_STATE_SCALING; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_REQ_STATUS: /* Report current status, sample rate, and resolution. */ //@todo: buttons u8Val = pThis->u8State; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, u8Val); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8Resolution); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8SampleRate); pThis->u8CurrCmd = 0; break; case ACMD_SET_STREAM: pThis->u8State &= ~AUX_STATE_REMOTE; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_RESET_WRAP: pThis->enmMode = AUX_MODE_STD; /* NB: Stream mode reporting remains disabled! */ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_WRAP: pThis->enmMode = AUX_MODE_WRAP; pThis->u8State &= ~AUX_STATE_ENABLED; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_REMOTE: pThis->u8State |= AUX_STATE_REMOTE; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_READ_ID: ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->enmProtocol); pThis->u8CurrCmd = 0; break; case ACMD_ENABLE: pThis->u8State |= AUX_STATE_ENABLED; //@todo: R3 only! #ifdef IN_RING3 ps2mSetDriverState(pThis, true); #endif ps2kClearQueue((GeneriQ *)&pThis->evtQ); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_DFLT_DISABLE: ps2mSetDefaults(pThis); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_DEFAULT: ps2mSetDefaults(pThis); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_RESEND: pThis->u8CurrCmd = 0; break; case ACMD_RESET: ps2mSetDefaults(pThis); ///@todo reset more? pThis->u8CurrCmd = cmd; pThis->enmMode = AUX_MODE_RESET; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); /* Slightly delay reset completion; it might take hundreds of ms. */ TMTimerSetMillies(pThis->CTX_SUFF(pDelayTimer), 1); break; /* The following commands need a parameter. */ case ACMD_SET_RES: case ACMD_SET_SAMP_RATE: ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = cmd; break; default: /* Sending a command instead of a parameter starts the new command. */ switch (pThis->u8CurrCmd) { case ACMD_SET_RES: //@todo reject unsupported resolutions pThis->u8Resolution = cmd; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_SAMP_RATE: //@todo reject unsupported rates ps2mSetRate(pThis, cmd); ps2mRateProtocolKnock(pThis, cmd); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; default: fHandled = false; } /* Fall through only to handle unrecognized commands. */ if (fHandled) break; case ACMD_INVALID_1: case ACMD_INVALID_2: case ACMD_INVALID_3: case ACMD_INVALID_4: case ACMD_INVALID_5: case ACMD_INVALID_6: case ACMD_INVALID_7: case ACMD_INVALID_8: case ACMD_INVALID_9: case ACMD_INVALID_10: Log(("Unsupported command 0x%02X!\n", cmd)); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND); pThis->u8CurrCmd = 0; break; } LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd)); // KBCUpdateInterrupts(pThis->pParent); return VINF_SUCCESS; }
/** * Receive and process a byte sent by the keyboard controller. * * @param pThis The PS/2 auxiliary device instance data. * @param cmd The command (or data) byte. */ int PS2MByteToAux(PPS2M pThis, uint8_t cmd) { uint8_t u8Val; bool fHandled = true; LogFlowFunc(("cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd)); //LogRel(("aux: cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd)); if (pThis->enmMode == AUX_MODE_RESET) /* In reset mode, do not respond at all. */ return VINF_SUCCESS; /* If there's anything left in the command response queue, trash it. */ ps2kClearQueue((GeneriQ *)&pThis->cmdQ); if (pThis->enmMode == AUX_MODE_WRAP) { /* In wrap mode, bounce most data right back.*/ if (cmd == ACMD_RESET || cmd == ACMD_RESET_WRAP) ; /* Handle as regular commands. */ else { ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, cmd); return VINF_SUCCESS; } } #ifndef IN_RING3 /* Reset, Enable, and Set Default commands must be run in R3. */ if (cmd == ACMD_RESET || cmd == ACMD_ENABLE || cmd == ACMD_SET_DEFAULT) return VINF_IOM_R3_IOPORT_WRITE; #endif switch (cmd) { case ACMD_SET_SCALE_11: pThis->u8State &= ~AUX_STATE_SCALING; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_SCALE_21: pThis->u8State |= AUX_STATE_SCALING; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_REQ_STATUS: /* Report current status, sample rate, and resolution. */ u8Val = (pThis->u8State & AUX_STATE_EXTERNAL) | (pThis->fCurrB & PS2M_STD_BTN_MASK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, u8Val); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8Resolution); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8SampleRate); pThis->u8CurrCmd = 0; break; case ACMD_SET_STREAM: pThis->u8State &= ~AUX_STATE_REMOTE; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_READ_REMOTE: ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->cmdQ, false); pThis->u8CurrCmd = 0; break; case ACMD_RESET_WRAP: pThis->enmMode = AUX_MODE_STD; /* NB: Stream mode reporting remains disabled! */ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_WRAP: pThis->enmMode = AUX_MODE_WRAP; pThis->u8State &= ~AUX_STATE_ENABLED; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_REMOTE: pThis->u8State |= AUX_STATE_REMOTE; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_READ_ID: ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->enmProtocol); pThis->u8CurrCmd = 0; break; case ACMD_ENABLE: pThis->u8State |= AUX_STATE_ENABLED; #ifdef IN_RING3 ps2mSetDriverState(pThis, true); #else AssertLogRelMsgFailed(("Invalid ACMD_ENABLE outside R3!\n")); #endif ps2kClearQueue((GeneriQ *)&pThis->evtQ); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_DISABLE: pThis->u8State &= ~AUX_STATE_ENABLED; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_SET_DEFAULT: ps2mSetDefaults(pThis); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; break; case ACMD_RESEND: pThis->u8CurrCmd = 0; break; case ACMD_RESET: ps2mSetDefaults(pThis); /// @todo reset more? pThis->u8CurrCmd = cmd; pThis->enmMode = AUX_MODE_RESET; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); if (pThis->fDelayReset) /* Slightly delay reset completion; it might take hundreds of ms. */ TMTimerSetMillies(pThis->CTX_SUFF(pDelayTimer), 1); else #ifdef IN_RING3 ps2mReset(pThis); #else AssertLogRelMsgFailed(("Invalid ACMD_RESET outside R3!\n")); #endif break; /* The following commands need a parameter. */ case ACMD_SET_RES: case ACMD_SET_SAMP_RATE: ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = cmd; break; default: /* Sending a command instead of a parameter starts the new command. */ switch (pThis->u8CurrCmd) { case ACMD_SET_RES: if (cmd < 4) /* Valid resolutions are 0-3. */ { pThis->u8Resolution = cmd; pThis->u8State &= ~AUX_STATE_RES_ERR; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; } else { /* Bad resolution. Reply with Resend or Error. */ if (pThis->u8State & AUX_STATE_RES_ERR) { pThis->u8State &= ~AUX_STATE_RES_ERR; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ERROR); pThis->u8CurrCmd = 0; } else { pThis->u8State |= AUX_STATE_RES_ERR; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND); /* NB: Current command remains unchanged. */ } } break; case ACMD_SET_SAMP_RATE: if (ps2mIsRateSupported(cmd)) { pThis->u8State &= ~AUX_STATE_RATE_ERR; ps2mSetRate(pThis, cmd); ps2mRateProtocolKnock(pThis, cmd); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); pThis->u8CurrCmd = 0; } else { /* Bad rate. Reply with Resend or Error. */ if (pThis->u8State & AUX_STATE_RATE_ERR) { pThis->u8State &= ~AUX_STATE_RATE_ERR; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ERROR); pThis->u8CurrCmd = 0; } else { pThis->u8State |= AUX_STATE_RATE_ERR; ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND); /* NB: Current command remains unchanged. */ } } break; default: fHandled = false; } /* Fall through only to handle unrecognized commands. */ if (fHandled) break; case ACMD_INVALID_1: case ACMD_INVALID_2: case ACMD_INVALID_3: case ACMD_INVALID_4: case ACMD_INVALID_5: case ACMD_INVALID_6: case ACMD_INVALID_7: case ACMD_INVALID_8: case ACMD_INVALID_9: case ACMD_INVALID_10: Log(("Unsupported command 0x%02X!\n", cmd)); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND); pThis->u8CurrCmd = 0; break; } LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd)); // KBCUpdateInterrupts(pThis->pParent); return VINF_SUCCESS; }