| /* |
| * Copyright (C) 2014 Google, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.projectara.araepm; |
| |
| import java.io.IOException; |
| |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.SystemClock; |
| import android.util.Log; |
| |
| import static android.content.Context.I2C_SERVICE; |
| |
| import android.hardware.I2cManager; |
| |
| public class EpmController { |
| |
| // EPM mailbox semantics: |
| // |
| // - Bit[31]: set this to indicate need to change attach state. |
| // (If this is already set, a state change is ongoing. |
| // This shouldn't happen under ordinary circumstances.) |
| // |
| // - Bit[30]: if set, attach the EPM. If unset, detach the EPM. |
| // |
| // - Bits[7:0]: port to change attach state of. |
| // |
| // After executing the EPM action, the SVC will clear bit 31 and |
| // put a result code into Bits[15:0], which is zero on success. |
| |
| // How long to wait for an EPM state change (milliseconds). |
| private static final long TIMEOUT_MS = 10000; |
| |
| // The EPM mailbox UniPro attribute. |
| private static final DME.AttributeId MBOX = DME.AttributeId.DME_MAILBOX; |
| |
| // The application processort port |
| // |
| // FIXME we should read this from the DME so we know where we are. |
| // Is there a way? Does it matter? |
| private static final int HACK_MY_PORT = 0; |
| |
| private static final int MAX_ENDO_PORT = 0xff; |
| private static final int MIN_ENDO_PORT = 0; |
| |
| private static final String TAG = "araepm"; |
| |
| public static enum EpmState { |
| EPM_ATTACH, |
| EPM_DETACH; |
| } |
| |
| public static enum EpmChangeStatus { |
| EPM_OK, |
| EPM_ERROR, |
| EPM_TIMEOUT; |
| } |
| |
| public interface EpmChangeCallback { |
| public void onEpmChangeFinished(EpmState state, |
| EpmChangeStatus status, |
| int port); |
| } |
| |
| private DME dme; |
| private EpmChangeCallback callback; |
| |
| private HandlerThread handlerThread; |
| private Handler handler; |
| |
| public EpmController(Context context, |
| EpmChangeCallback callback) { |
| I2cManager i2c = (I2cManager)context.getSystemService(I2C_SERVICE); |
| this.dme = new DME(i2c); |
| this.callback = callback; |
| } |
| |
| private boolean isStartedLocked() { |
| return handlerThread != null; |
| } |
| |
| private void ensureStartedLocked() { |
| if (!isStartedLocked()) { |
| throw new IllegalStateException("not yet started"); |
| } |
| } |
| |
| public synchronized void start() { |
| if (isStartedLocked()) { |
| throw new IllegalStateException("already started"); |
| } |
| handlerThread = new HandlerThread("EpmController"); |
| handlerThread.start(); |
| Looper looper = handlerThread.getLooper(); |
| if (looper == null) { |
| Log.wtf(TAG, "can't get handler thread looper"); |
| throw new RuntimeException(); |
| } |
| handler = new Handler(looper); |
| } |
| |
| public synchronized void stop() { |
| if (!isStartedLocked()) { |
| throw new IllegalStateException("not yet started"); |
| } |
| handlerThread.quit(); |
| while (handlerThread.isAlive()) |
| ; |
| handler = null; |
| } |
| |
| private void doAttachDetachLocked(int port, EpmState desired) { |
| if (port > MAX_ENDO_PORT || port < MIN_ENDO_PORT) { |
| Log.wtf(TAG, "Invalid port value: " + port); |
| callback.onEpmChangeFinished(desired, EpmChangeStatus.EPM_ERROR, |
| port); |
| return; |
| } |
| |
| ensureStartedLocked(); |
| handler.post(new EpmStateChanger(port, desired)); |
| } |
| |
| public synchronized void attachEpm(int port) { |
| doAttachDetachLocked(port, EpmState.EPM_ATTACH); |
| } |
| |
| public synchronized void detachEpm(int port) { |
| doAttachDetachLocked(port, EpmState.EPM_DETACH); |
| } |
| |
| private class EpmStateChanger implements Runnable { |
| private final int port; |
| private final EpmState desired; |
| private final int mBoxValue; |
| |
| private static final int S_IDX = 0; // selector index |
| private static final int CHANGING_BIT = 31; |
| private static final int ATTACH_BIT = 30; |
| private static final int PORT_MASK = 0xff; |
| private static final int RESULT_MASK = 0xffff; |
| |
| private static final boolean PEER_MBOX = true; |
| |
| public EpmStateChanger(int port, EpmState desired) { |
| this.port = port; |
| this.desired = desired; |
| this.mBoxValue = ((1 << CHANGING_BIT) | |
| ((desired == EpmState.EPM_ATTACH ? 1 : 0) |
| << ATTACH_BIT) | |
| (port & PORT_MASK)); |
| } |
| |
| private int getMBox() throws IOException { |
| return dme.readDMEConfig(HACK_MY_PORT, PEER_MBOX, MBOX, S_IDX, 0); |
| } |
| |
| private boolean isEpmChangeOngoing(int mbox) { |
| return (mbox & (1 << CHANGING_BIT)) != 0; |
| } |
| |
| private void startStateChange() throws IOException { |
| dme.writeDMEConfig(HACK_MY_PORT, PEER_MBOX, MBOX, S_IDX, mBoxValue, |
| 0); |
| } |
| |
| private void finish(EpmChangeStatus status) { |
| callback.onEpmChangeFinished(desired, status, port); |
| } |
| |
| @Override |
| public void run() { |
| long start = SystemClock.elapsedRealtime(); |
| boolean started = false; |
| |
| // FIXME try/finally a wake lock here -- this is a bad |
| // time to go to sleep |
| while (true) { |
| int mbox; |
| |
| // Bail on timeout. |
| if (SystemClock.elapsedRealtime() - start > TIMEOUT_MS) { |
| finish(EpmChangeStatus.EPM_TIMEOUT); |
| return; |
| } |
| |
| // Check the mailbox. |
| try { |
| mbox = getMBox(); |
| } catch (IOException ioe) { |
| Log.e(TAG, "can't read EPM mailbox: " + ioe); |
| finish(EpmChangeStatus.EPM_ERROR); |
| return; |
| } |
| |
| // Try to start the EPM state change, allowing for |
| // a previously ongoing state change to continue. |
| if (!started) { |
| if (!isEpmChangeOngoing(mbox)) { |
| try { |
| startStateChange(); |
| } catch (IOException ioe) { |
| Log.e(TAG, "can't request EPM change: " + ioe); |
| finish(EpmChangeStatus.EPM_ERROR); |
| return; |
| } |
| started = true; |
| } |
| continue; |
| } |
| |
| // Wait for the change to finish, and relay the result |
| // to the callback. |
| if (isEpmChangeOngoing(mbox)) { |
| continue; |
| } |
| int exitStatus = mbox & RESULT_MASK; |
| finish(exitStatus == 0 ? |
| EpmChangeStatus.EPM_OK : |
| EpmChangeStatus.EPM_ERROR); |
| return; |
| } |
| } |
| } |
| } |