/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.driver.ps2; import java.security.PrivilegedExceptionAction; import javax.naming.NameNotFoundException; import org.apache.log4j.Logger; import org.jnode.driver.Bus; import org.jnode.driver.DeviceException; import org.jnode.driver.DriverException; import org.jnode.naming.InitialNaming; import org.jnode.system.resource.IOResource; import org.jnode.system.resource.IRQHandler; import org.jnode.system.resource.IRQResource; import org.jnode.system.resource.ResourceManager; import org.jnode.system.resource.ResourceNotFreeException; import org.jnode.system.resource.ResourceOwner; import org.jnode.util.AccessControllerUtils; import org.jnode.util.NumberUtils; /** * Provides common functionality shared by the drivers (read/write data/status) * * @author qades */ public class PS2Bus extends Bus implements IRQHandler, PS2Constants { /** My logger */ private static final Logger log = Logger.getLogger(PS2Bus.class); private IOResource ioResData; private IOResource ioResCtrl; private int activeCount = 0; private final PS2ByteChannel kbChannel = new PS2ByteChannel(); private final PS2ByteChannel mouseChannel = new PS2ByteChannel(); /** * Create a PS2 object. */ PS2Bus(Bus parent) { super(parent); } /** * All necessary resources are claimed in this method. * * @param owner the driver for which the resources are to be claimed * @return the IRQResource for the driver in question */ final synchronized IRQResource claimResources(ResourceOwner owner, int irq) throws DriverException { try { final ResourceManager rm; try { rm = InitialNaming.lookup(ResourceManager.NAME); } catch (NameNotFoundException ex) { throw new DriverException("Cannot find ResourceManager: ", ex); } if (ioResData == null) { ioResData = claimPorts(rm, owner, PS2_DATA_PORT, 1); ioResCtrl = claimPorts(rm, owner, PS2_CTRL_PORT, 1); } final IRQResource irqRes = rm.claimIRQ(owner, irq, this, true); if (activeCount == 0) { flush(); } activeCount++; return irqRes; } catch (ResourceNotFreeException ex) { throw new DriverException("Cannot claim necessairy resources: ", ex); } } /** * Release all resource held by this bus, only if all devices that depend on * this bus have been stopped. */ final synchronized void releaseResources() { activeCount--; if (activeCount == 0) { ioResData.release(); ioResCtrl.release(); ioResData = null; ioResCtrl = null; } } /** * Handles a PS/2 interrupt * * @see org.jnode.system.resource.IRQHandler#handleInterrupt(int) */ public final synchronized void handleInterrupt(int irq) { processQueues(); } /** * Read the queue until it is empty and process the read data. */ private final void processQueues() { int status; int loops = 0; while (((status = readStatus()) & STAT_OBF) != 0) { final int data = readData(); if (++loops > 1000) { log.error("A lot of PS2 data, probably wrong, dropping it"); continue; } // determine which driver shall handle the scancode final PS2ByteChannel channel; if ((status & STAT_MOUSE_OBF) != 0) { channel = mouseChannel; } else { channel = kbChannel; } // if this driver is not registered, merely exit if (channel == null) { log.debug("Unhandled scancode 0x" + Integer.toHexString(data) + " status=0x" + Integer.toHexString(status)); } else { // let the driver handle the scancode channel.handleScancode(data); } } } /** * Flush the PS2 output buffer. */ final synchronized void flush() { while ((readStatus() & STAT_OBF) != 0) { readData(); } } /** * Get the status of the PS/2 port. * * @return the status byte */ final int readStatus() { return ioResCtrl.inPortByte(PS2_STAT_PORT) & 0xff; } /** * Write a byte to the control port * * @param b */ private void writeController(int b) throws DeviceException { waitWrite(); ioResCtrl.outPortByte(PS2_CTRL_PORT, b); } /** * Write a byte to the data port * * @param b */ private final void writeData(int b) throws DeviceException { waitWrite(); ioResData.outPortByte(PS2_DATA_PORT, b); } /** * Wait for a data available in outputbuffer. */ private final boolean waitRead() { int count = 0; while (count < 1000) { if ((readStatus() & STAT_OBF) != 0) { return true; } else { count++; Thread.yield(); } } return false; } /** * Wait for a non-ready inputbuffer. */ private final void waitWrite() throws DeviceException { int count = 0; while (count < 1000) { if ((readStatus() & STAT_IBF) == 0) { return; } else { count++; Thread.yield(); } } throw new DeviceException("InputBuffer full"); } /** * Read a byte from the data port * * @return */ private final int readData() { return ioResData.inPortByte(PS2_DATA_PORT) & 0xff; } /** * Gets the mode register * * @return int */ final synchronized int getMode() throws DeviceException { try { writeController(CCMD_READ_MODE); if (waitRead()) { return readData(); } else { throw new DeviceException("Not return data from READ_MODE"); } } finally { processQueues(); } } /** * Sets the mode register */ final void setMode(int mode) throws DeviceException { writeController(CCMD_WRITE_MODE); writeData(mode); } /** * Test for the presence of a connected mouse device * * @return true if a mouse is present, false if not */ final synchronized boolean testMouse() throws DeviceException { try { writeController(CCMD_TEST_MOUSE); if (waitRead()) { final int status = readStatus(); final int rc = readData(); log.debug("testMouse rc=0x" + NumberUtils.hex(rc, 2) + ", status 0x" + NumberUtils.hex(status, 2)); return (rc != 0xFF); } else { log.debug("No return from TEST_MOUSE"); return false; } } finally { processQueues(); } } /** * Activate/Deactivate the mouse */ final void setMouseEnabled(boolean enable) throws DeviceException { writeController(enable ? CCMD_MOUSE_ENABLE : CCMD_MOUSE_DISABLE); int mode = getMode(); if (enable) { mode |= MODE_MOUSE_INT; } else { mode &= ~MODE_MOUSE_INT; } setMode(mode); } /** * Activate/Deactivate the keyboard */ final void setKeyboardEnabled(boolean enable) throws DeviceException { writeController(enable ? CCMD_KB_ENABLE : CCMD_KB_DISABLE); int mode = getMode(); if (enable) { mode |= MODE_INT; } else { mode &= ~MODE_INT; } setMode(mode); } /** * Read an ACK and cnt bytes. The bytes (except the ACK) are added to the * given channel. * * @param channel * @param cnt * @return */ private final boolean readAckAndData(PS2ByteChannel channel, int cnt) { if (!waitRead()) { return false; } int data = readData(); switch (data) { case REPLY_ACK: break; case REPLY_RESEND: log.error("Mouse replied with RESEND"); return false; default: // Not an ACK, consider it data channel.handleScancode(data); cnt--; break; } while (cnt > 0) { if (!waitRead()) { return false; } data = readData(); channel.handleScancode(data); cnt--; } return true; } /** * Send a single byte command to the mouse * * @param cmd * @return Success (true / false) */ private final boolean sendMouseCommand(int cmd, int returnCnt) throws DeviceException { // Transmit the command writeController(CCMD_WRITE_MOUSE); writeData(cmd); return readAckAndData(mouseChannel, returnCnt); } /** * Write a series of commands to the mouse * * @param cmd The command * @param params The command parameters * @return */ final synchronized boolean writeMouseCommands(int cmd, int[] params, int returnCnt) throws DeviceException { // First clear the mouse channel, otherwise we might read // old data back mouseChannel.clear(); if (params == null) { return sendMouseCommand(cmd, returnCnt); } else { if (!sendMouseCommand(cmd, 0)) { return false; } final int cnt = params.length; for (int i = 0; i < cnt - 1; i++) { if (!sendMouseCommand(params[i], 0)) { return false; } } return sendMouseCommand(params[cnt - 1], returnCnt); } } /** * @return */ final PS2ByteChannel getKbChannel() { return this.kbChannel; } /** * @return */ final PS2ByteChannel getMouseChannel() { return this.mouseChannel; } private IOResource claimPorts(final ResourceManager rm, final ResourceOwner owner, final int low, final int length) throws ResourceNotFreeException, DriverException { try { return AccessControllerUtils.doPrivileged(new PrivilegedExceptionAction<IOResource>() { public IOResource run() throws ResourceNotFreeException { return rm.claimIOResource(owner, low, length); } }); } catch (ResourceNotFreeException ex) { throw ex; } catch (Exception ex) { throw new DriverException("Unknown exception", ex); } } }