/* Reset the pointing device. */ static void ps2mReset(PPS2M pThis) { ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_BAT_OK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, 0); pThis->enmMode = AUX_MODE_STD; pThis->u8CurrCmd = 0; /// @todo move to its proper home! ps2mSetDriverState(pThis, true); }
/** * 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; }
/* Report accumulated movement and button presses, then clear the accumulators. */ static void ps2mReportAccumulatedEvents(PPS2M pThis, GeneriQ *pQueue, bool fAccumBtns) { uint32_t fBtnState = fAccumBtns ? pThis->fAccumB : pThis->fCurrB; uint8_t val; int dX, dY, dZ; /* Clamp the accumulated delta values to the allowed range. */ dX = RT_MIN(RT_MAX(pThis->iAccumX, -255), 255); dY = RT_MIN(RT_MAX(pThis->iAccumY, -255), 255); dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7); /* Start with the sync bit and buttons 1-3. */ val = RT_BIT(3) | (fBtnState & PS2M_STD_BTN_MASK); /* Set the X/Y sign bits. */ if (dX < 0) val |= RT_BIT(4); if (dY < 0) val |= RT_BIT(5); /* Send the standard 3-byte packet (always the same). */ ps2kInsertQueue(pQueue, val); ps2kInsertQueue(pQueue, dX); ps2kInsertQueue(pQueue, dY); /* Add fourth byte if extended protocol is in use. */ if (pThis->enmProtocol > PS2M_PROTO_PS2STD) { if (pThis->enmProtocol == PS2M_PROTO_IMPS2) ps2kInsertQueue(pQueue, dZ); else { Assert(pThis->enmProtocol == PS2M_PROTO_IMEX); /* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */ val = dZ & 0x0f; val |= (fBtnState << 1) & (RT_BIT(4) | RT_BIT(5)); ps2kInsertQueue(pQueue, val); } } /* Clear the movement accumulators, but not necessarily button state. */ pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = 0; /* Clear accumulated button state only when it's being used. */ if (fAccumBtns) { pThis->fReportedB = pThis->fAccumB; pThis->fAccumB = 0; } }
/* Report accumulated movement and button presses, then clear the accumulators. */ static void ps2mReportAccumulatedEvents(PPS2M pThis) { uint8_t val; int8_t dX, dY, dZ; /* Clamp the accumulated delta values to the allowed range. */ dX = RT_MIN(RT_MAX(pThis->iAccumX, -256), 255); dY = RT_MIN(RT_MAX(pThis->iAccumY, -256), 255); dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7); /* Start with the sync bit and buttons 1-3. */ val = RT_BIT(3) | (pThis->fAccumB & PS2M_STD_BTN_MASK); /* Set the X/Y sign bits. */ if (dX < 0) val |= RT_BIT(4); if (dY < 0) val |= RT_BIT(5); /* Send the standard 3-byte packet (always the same). */ ps2kInsertQueue((GeneriQ *)&pThis->evtQ, val); ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dX); ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dY); /* Add fourth byte if extended protocol is in use. */ if (pThis->enmProtocol > PS2M_PROTO_PS2STD) { if (pThis->enmProtocol == PS2M_PROTO_IMPS2) ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dZ); else { Assert(pThis->enmProtocol == PS2M_PROTO_IMEX); /* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */ val = dZ & 0x0f; val |= (pThis->fAccumB << 1) & (RT_BIT(4) | RT_BIT(5)); ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dZ); } } /* Clear the accumulators. */ pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = pThis->fAccumB = 0; /* Poke the KBC to update its state. */ KBCUpdateInterrupts(pThis->pParent); }
/* The auxiliary device is specified to take up to about 500 milliseconds. We need * to delay sending the result to the host for at least a tiny little while. */ static DECLCALLBACK(void) ps2mDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { PPS2M pThis = (PS2M *)pvUser; NOREF(pDevIns); LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd)); Assert(pThis->u8CurrCmd == ACMD_RESET); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_BAT_OK); ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, 0); pThis->enmMode = AUX_MODE_STD; pThis->u8CurrCmd = 0; ///@todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC... /* Give the KBC a kick. */ KBCUpdateInterrupts(pThis->pParent); //@todo: move to its proper home! ps2mSetDriverState(pThis, true); }
/** * 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; }