@Override public void write(int ireg, byte[] data, boolean waitForCompletion) { try { synchronized (this.concurrentClientLock) { synchronized (this.callbackLock) { // Wait until we can write to the write cache while (this.writeCacheStatus != WRITE_CACHE_STATUS.IDLE) { this.callbackLock.wait(); } // Indicate where we want to write this.iregWriteFirst = ireg; this.cregWrite = data.length; // Indicate we are dirty so the callback will write us out this.writeCacheStatus = WRITE_CACHE_STATUS.DIRTY; // Provide the data we want to write this.writeCacheLock.lockInterruptibly(); try { System.arraycopy(data, 0, this.writeCache, dibCacheOverhead, data.length); } finally { this.writeCacheLock.unlock(); } // Let the callback know we've got new data for him this.callback.onNewDataToWrite(); if (waitForCompletion) { // Wait until the write at least issues to the device controller. This will // help make any delays/sleeps that follow a write() be more deterministically // relative to the actual I2C device write. while (writeCacheStatus != WRITE_CACHE_STATUS.IDLE) { this.callbackLock.wait(); } } } } } catch (InterruptedException e) { Util.handleCapturedInterrupt(e); } }
void updateStateMachines(UPDATE_STATE_MACHINE caller) // We've got quite the little state machine here! { synchronized (callbackLock) { // ---------------------------------------------------------------------------------- // If we're calling from other than the callback (in which we *know* the port is // ready), we need to check whether things are currently busy. We defer until // later if they are. if (caller == UPDATE_STATE_MACHINE.FROM_USER_WRITE) { if (!i2cDevice.isI2cPortReady() || callbackThread == null) return; // Optimized calling from user mode is not yet implemented return; } // ---------------------------------------------------------------------------------- // Some ancillary bookkeeping if (caller == UPDATE_STATE_MACHINE.FROM_CALLBACK) { // Capture the current callback thread if we haven't already if (callbackThread == null) { callbackThread = Thread.currentThread(); callbackThreadOriginalPriority = callbackThread.getPriority(); } else assertTrue( !BuildConfig.DEBUG || callbackThread.getId() == Thread.currentThread().getId()); // Set the thread name to make the system more debuggable if (0 == hardwareCycleCount) Thread.currentThread().setName(String.format("RWLoop(%s)", i2cDevice.getDeviceName())); // Adjust the target thread priority. Note that we only ever adjust it upwards, // not downwards, because in reality the thread is shared by other I2C objects // on the same controller and we don't want to fight with their understanding // of what the priority should be. int targetPriority = callbackThreadOriginalPriority + callbackThreadPriorityBoost; if (callbackThread.getPriority() < targetPriority) { try { callbackThread.setPriority(targetPriority); } catch (Exception e) { /* ignore: just run as is */ } } // Update cycle statistics hardwareCycleCount++; } // ---------------------------------------------------------------------------------- // Initialize state for managing state transition setActionFlag = false; queueFullWrite = false; queueRead = false; heartbeatRequired = (msHeartbeatInterval > 0 && milliseconds(timeSinceLastHeartbeat) >= msHeartbeatInterval); enabledReadMode = false; enabledWriteMode = false; prevReadCacheStatus = readCacheStatus; prevWriteCacheStatus = writeCacheStatus; prevModeCacheStatus = modeCacheStatus; // ---------------------------------------------------------------------------------- // Handle the state machine if (caller == UPDATE_STATE_MACHINE.FROM_CALLBACK) { // -------------------------------------------------------------------------- // Deal with the fact that we've completed any previous queueing operation if (modeCacheStatus == MODE_CACHE_STATUS.QUEUED) modeCacheStatus = MODE_CACHE_STATUS.IDLE; if (readCacheStatus == READ_CACHE_STATUS.QUEUED || readCacheStatus == READ_CACHE_STATUS.VALID_QUEUED) { readCacheStatus = READ_CACHE_STATUS.QUEUE_COMPLETED; nanoTimeReadCacheValid = System.nanoTime(); } if (writeCacheStatus == WRITE_CACHE_STATUS.QUEUED) { writeCacheStatus = WRITE_CACHE_STATUS.IDLE; // Our write mode status should have been reported back to us assertTrue(!BuildConfig.DEBUG || i2cDevice.isI2cPortInWriteMode()); } // -------------------------------------------------------------------------- // That limits the number of states the caches can now be in assertTrue( !BuildConfig.DEBUG || (readCacheStatus == READ_CACHE_STATUS.IDLE || readCacheStatus == READ_CACHE_STATUS.SWITCHINGTOREADMODE || readCacheStatus == READ_CACHE_STATUS.VALID_ONLYONCE || readCacheStatus == READ_CACHE_STATUS.QUEUE_COMPLETED)); assertTrue( !BuildConfig.DEBUG || (writeCacheStatus == WRITE_CACHE_STATUS.IDLE || writeCacheStatus == WRITE_CACHE_STATUS.DIRTY)); // -------------------------------------------------------------------------- // Complete any read mode switch if there is one if (readCacheStatus == READ_CACHE_STATUS.SWITCHINGTOREADMODE) { // We're trying to switch into read mode. Are we there yet? if (i2cDevice.isI2cPortInReadMode()) { // See also below XYZZY readCacheStatus = READ_CACHE_STATUS.QUEUED; setActionFlag = true; // actually do an I2C read queueRead = true; // read the I2C read results } else { queueRead = true; // read the mode byte } } // -------------------------------------------------------------------------- // If there's a write request pending, and it's ok to issue the write, do so else if (writeCacheStatus == WRITE_CACHE_STATUS.DIRTY) { issueWrite(); // Our ordering rules are that any reads after a write have to wait until // the write is actually sent to the hardware, so anything we've read before is junk. // Note that there's an analogous check in read(). readCacheStatus = READ_CACHE_STATUS.IDLE; } // -------------------------------------------------------------------------- // Initiate reading if we should. Be sure to honor the policy of the read mode else if (readCacheStatus == READ_CACHE_STATUS.IDLE || readWindowChanged) { if (readWindow != null && readWindow.isOkToRead()) { // We're going to read from this window. If it's an only-once, then // ensure we don't come down this path again with the same ReadWindow instance. readWindow.setReadIssued(); // You know...we might *already* have set up the controller to read what we want. // Maybe the previous read was a one-shot, for example. if (readWindowSentToController != null && readWindowSentToController.contains(readWindow) && i2cDevice.isI2cPortInReadMode()) { // Lucky us! We can go ahead and queue the read right now! // See also above XYZZY readWindowActuallyRead = readWindowSentToController; readCacheStatus = READ_CACHE_STATUS.QUEUED; setActionFlag = true; // actually do an I2C read queueRead = true; // read the results of the read } else { // We'll start switching now, and queue the read later readWindowActuallyRead = readWindow; startSwitchingToReadMode(readWindow); } } else { // There's nothing to read. Make *sure* we are idle. readCacheStatus = READ_CACHE_STATUS.IDLE; } readWindowChanged = false; } // -------------------------------------------------------------------------- // Reissue any previous read if we should. The only way we are here and // see READ_CACHE_STATUS.QUEUE_COMPLETED is if we completed a queuing operation // above. else if (readCacheStatus == READ_CACHE_STATUS.QUEUE_COMPLETED) { if (readWindow != null && readWindow.isOkToRead()) { readCacheStatus = READ_CACHE_STATUS.VALID_QUEUED; setActionFlag = true; // actually do an I2C read queueRead = true; // read the results of the read } else { readCacheStatus = READ_CACHE_STATUS.VALID_ONLYONCE; } } // -------------------------------------------------------------------------- // Completing the possibilities: else if (readCacheStatus == READ_CACHE_STATUS.VALID_ONLYONCE) { // Just leave it there until someone reads it } // ---------------------------------------------------------------------------------- // Ok, after all that we finally know what how we're required to // interact with the device controller according to what we've been // asked to read or write. But what, now, about heartbeats? if (!setActionFlag && heartbeatRequired) { if (heartbeatAction != null) { if (readWindowSentToController != null && heartbeatAction.rereadLastRead) { // Controller is in or is switching to read mode. If he's there // yet, then issue an I2C read; if he's not, then he soon will be. if (i2cDevice.isI2cPortInReadMode()) { setActionFlag = true; // issue an I2C read } else { assertTrue( !BuildConfig.DEBUG || readCacheStatus == READ_CACHE_STATUS.SWITCHINGTOREADMODE); } } else if (readWindowSentToControllerInitialized && readWindowSentToController == null && heartbeatAction.rewriteLastWritten) { // Controller is in write mode, and the write cache has what we last wrote queueFullWrite = true; setActionFlag = true; // issue an I2C write } else if (heartbeatAction.heartbeatReadWindow != null) { // The simplest way to do this is just to do a new read from the outside, as that // means it has literally zero impact here on our state machine. That unfortunately // introduces concurrency where otherwise none might exist, but that's ONLY if you // choose this flavor of heartbeat, so that's a reasonable tradeoff. final ReadWindow window = heartbeatAction .heartbeatReadWindow; // capture here while we still have the lock Thread thread = new Thread( new Runnable() { @Override public void run() { try { I2cDeviceClient.this.read(window.getIregFirst(), window.getCreg()); } catch (Exception e) // paranoia { // ignored } } }); // Start the thread a-going. It will run relatively quickly and then shut down thread.setName("I2C heartbeat read thread"); thread.setPriority(heartbeatAction.explicitReadPriority); thread.start(); } } } if (setActionFlag) { // We're about to communicate on I2C right now, so reset the heartbeat. // Note that we reset() *before* we talk to the device so as to do // conservative timing accounting. timeSinceLastHeartbeat.reset(); } } else if (caller == UPDATE_STATE_MACHINE.FROM_USER_WRITE) { // There's nothing we know to do that would speed things up, so we // just do nothing here and wait until the next portIsReady() callback. } // ---------------------------------------------------------------------------------- // Read, set action flag and / or queue to module as requested if (setActionFlag) i2cDevice.setI2cPortActionFlag(); else clearActionFlag(); if (setActionFlag && !queueFullWrite) { i2cDevice.writeI2cPortFlagOnlyToController(); } else if (queueFullWrite) { i2cDevice.writeI2cCacheToController(); // if (modeCacheStatus == MODE_CACHE_STATUS.DIRTY) modeCacheStatus = MODE_CACHE_STATUS.QUEUED; } // Queue a read after queuing any write for a bit of paranoia: if we're mode switching // to write, we want that write to go out first, THEN read the mode status. It probably // would anyway, but why not... if (queueRead) { i2cDevice.readI2cCacheFromController(); } // ---------------------------------------------------------------------------------- // Do logging if (loggingEnabled) { StringBuilder message = new StringBuilder(); switch (caller) { case FROM_CALLBACK: message.append(String.format("cyc %d", hardwareCycleCount)); break; case FROM_USER_WRITE: message.append(String.format("usr write")); break; } if (setActionFlag) message.append("|flag"); if (setActionFlag && !queueFullWrite) message.append("|f"); else if (queueFullWrite) message.append("|w"); else message.append("|."); if (queueRead) message.append("|r"); if (readCacheStatus != prevReadCacheStatus) message.append( "| R." + prevReadCacheStatus.toString() + "->" + readCacheStatus.toString()); if (writeCacheStatus != prevWriteCacheStatus) message.append( "| W." + prevWriteCacheStatus.toString() + "->" + writeCacheStatus.toString()); // if (modeCacheStatus != prevModeCacheStatus) message.append("| M." + // prevModeCacheStatus.toString() + "->" + modeCacheStatus.toString()); if (enabledWriteMode) message.append(String.format("| setWrite(0x%02x,%d)", iregWriteFirst, cregWrite)); if (enabledReadMode) message.append( String.format( "| setRead(0x%02x,%d)", readWindow.getIregFirst(), readWindow.getCreg())); log(Log.DEBUG, message.toString()); } // ---------------------------------------------------------------------------------- // Notify anyone blocked in read() or write() callbackLock.notifyAll(); } }