blob: 9b137f62e24c99b04a39044b4ac94dc4fee2b436 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
var twinkie = {};
// USB identifiers.
twinkie.USB_VENDOR_ID = 0x18d1;
twinkie.USB_PRODUCT_ID = 0x500a;
twinkie.EP_SNIFFER = 3;
twinkie.isTwinkie = function(device) {
return device.vendorId === twinkie.USB_VENDOR_ID &&
device.productId === twinkie.USB_PRODUCT_ID;
};
twinkie.Dongle = function(device) {
this.device_ = device;
this.enc_ = new TextEncoder('utf-8');
this.dec_ = new TextDecoder();
this.vbus_re_ = new RegExp('VBUS = (-?[0-9]+) mV ; (-?[0-9]+) mA');
this.tracing_mode_ = false;
console.log(device);
};
twinkie.Dongle.prototype.connect = function() {
let readLoop = () => {
this.device_.transferIn(twinkie.EP_SNIFFER, 64).then(result => {
if (this.tracing_mode_ && result.data.byteLength) {
var pkt = new Uint32Array(result.data.buffer);
var head = pkt[2] & 0xffff;
var cnt = (head >> 12) & 0x7;
var tstamp = pkt[0];
this.onPdPacket(tstamp, head, pkt.slice(3, 3 + cnt));
}
readLoop();
}, error => {
this.onReceiveError(error);
});
};
return this.device_.open()
.then(() => {
if (this.device_.configuration === null) {
return this.device_.selectConfiguration(1);
}
})
.then(() => {
// Default to the first interface if there is no vendor one
var ifaces = this.device_.configuration.interfaces;
var vendor = ifaces[0];
for (var iface = 0; iface < ifaces.length; iface++) {
if (ifaces[iface].alternates[0].interfaceClass === 255 &&
ifaces[iface].alternates[0].interfaceSubclass === 0) {
vendor = ifaces[iface];
break;
}
}
this.ifaceNum_ = vendor.interfaceNumber;
var eps = vendor.alternates[0].endpoints;
this.epIn_ = 1;
this.epOut_ = 1;
for (var ep = 0; ep < eps.length; ep++) {
if (eps[ep].direction === 'in' && eps[ep].type === 'bulk') {
this.epIn_ = eps[ep].endpointNumber;
} else if (eps[ep].direction === 'out' && eps[ep].type === 'bulk') {
this.epOut_ = eps[ep].endpointNumber;
}
}
})
.then(() => this.device_.claimInterface(this.ifaceNum_))
.then(() => this.device_.claimInterface(1))
.then(() => this.getVersion())
.then(ver => {
console.log('Version: ' + ver);
this.fwVersion = ver;
return this.getCopy();
})
.then(cpy => {
console.log('FW Copy: ' + cpy);
this.fwCopy = cpy;
readLoop();
});
};
twinkie.Dongle.prototype.disconnect = function() {
return this.device_.close();
};
twinkie.Dongle.prototype.send = function(text) {
return this.device_.transferOut(this.epOut_, this.enc_.encode(text).buffer);
};
twinkie.Dongle.prototype.command = function(cmd) {
return new Promise(function(resolve, reject) {
var resp = '';
let readResponse = () => {
this.device_.transferIn(this.epIn_, 64).then(result => {
if (result.status !== 'ok') {
reject('Read failed: ' + result.status);
}
var len = result.data.byteLength;
if (len) {
resp += this.dec_.decode(result.data);
}
if (len === 64) {
/* more to come */
readResponse();
} else {
/* response fully received */
resolve(resp);
}
}, error => {
reject('Read error: ' + error);
});
};
this.device_.transferOut(this.epOut_, this.enc_.encode(cmd).buffer)
.then(() => {
readResponse();
});
}.bind(this));
};
twinkie.Dongle.prototype.vbusDone = function(info) {
var mtch = info.match(this.vbus_re_);
if (mtch && mtch.length === 3) {
var vbus_pair = [mtch[1], mtch[2]];
this.onVBusMeasure(vbus_pair);
}
if (this.tracing_mode_ === true) {
this.command('tw vbus').then(this.vbusDone.bind(this));
}
};
twinkie.Dongle.prototype.trace_mode = function(enable) {
if (this.tracing_mode_ === enable) {
return;
}
if (enable) {
this.send('tw trace raw');
} else {
this.send('tw trace off');
}
this.tracing_mode_ = enable;
this.command('tw vbus').then(this.vbusDone.bind(this));
};
twinkie.Dongle.prototype.getVersion = function() {
return this.command('version').then(function(ver) {
var versionRE = new RegExp('^Build:\\s+(\\S+)$', 'm');
var mtch = ver.match(versionRE);
if (mtch && mtch.length === 2) {
return mtch[1];
}
return 'unknown';
});
};
twinkie.Dongle.prototype.getCopy = function() {
return this.command('sysinfo').then(function(info) {
var copyRE = new RegExp('^Copy:\\s+(\\S+)$', 'm');
var mtch = info.match(copyRE);
if (mtch && mtch.length === 2) {
return mtch[1];
}
return 'RO';
});
};
twinkie.Dongle.prototype.goPartition = function(part) {
this.send('sysjump ' + part);
};
twinkie.Dongle.prototype.readVbus = function() {
return this.command('tw vbus').then(function(info) {
var mtch = info.match(this.vbus_re_);
if (mtch && mtch.length === 3) {
var vbus_pair = [mtch[1], mtch[2]];
this.onVBusMeasure(vbus_pair);
return vbus_pair;
}
return [0, 0];
}.bind(this));
};