blob: 4923d7ee7f53055a78792177966a0bd38c24b11a [file] [log] [blame]
/*
* 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;
}
}
}
}