UNPKG

node-beaglebone-usbboot

Version:
344 lines 14.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); // tslint:disable:no-bitwise const usb = require("@balena.io/usb"); const _debug = require("debug"); const events_1 = require("events"); const _os = require("os"); const messages_1 = require("./messages"); const platform = _os.platform(); const debug = _debug('node-beaglebone-usbboot'); const POLLING_INTERVAL_MS = 2000; // Delay in ms after which we consider that the device was unplugged (not resetted) const DEVICE_UNPLUG_TIMEOUT = 5000; const USB_VENDOR_ID_TEXAS_INSTRUMENTS = 0x0451; const USB_PRODUCT_ID_ROM = 0x6141; const USB_PRODUCT_ID_SPL = 0xd022; const getDeviceId = (device) => { return `${device.busNumber}:${device.deviceAddress}`; }; exports.isUsbBootCapableUSBDevice = (idVendor, idProduct) => { return (idVendor === USB_VENDOR_ID_TEXAS_INSTRUMENTS && (idProduct === USB_PRODUCT_ID_ROM || idProduct === USB_PRODUCT_ID_SPL)); }; exports.isROMUSBDevice = (idVendor, idProduct) => { return idVendor === USB_VENDOR_ID_TEXAS_INSTRUMENTS && idProduct === USB_PRODUCT_ID_ROM; }; exports.isSPLUSBDevice = (idVendor, idProduct) => { return idVendor === USB_VENDOR_ID_TEXAS_INSTRUMENTS && idProduct === USB_PRODUCT_ID_SPL; }; const isUsbBootCapableUSBDevice$ = (device) => { return exports.isUsbBootCapableUSBDevice(device.deviceDescriptor.idVendor, device.deviceDescriptor.idProduct); }; const isBeagleBoneInMassStorageMode = (device) => { return (device.deviceDescriptor.idVendor === USB_VENDOR_ID_TEXAS_INSTRUMENTS && device.deviceDescriptor.idProduct === USB_PRODUCT_ID_SPL && device.deviceDescriptor.bNumConfigurations === 1); }; const initializeDevice = (device) => { debug('bInterface', device.configDescriptor.bNumInterfaces); const interfaceNumber = 1; const iface = device.interface(interfaceNumber); if (platform !== 'win32') { // Not supported in Windows // Detach Kernel Driver if (iface.isKernelDriverActive()) { iface.detachKernelDriver(); } } iface.claim(); const inEndpoint = iface.endpoints[0]; const outEndpoint = iface.endpoints[1]; if (!(inEndpoint instanceof usb.InEndpoint)) { throw new Error('endpoint is not an usb.OutEndpoint'); } if (!(outEndpoint instanceof usb.OutEndpoint)) { throw new Error('endpoint is not an usb.OutEndpoint'); } debug('Initialized device correctly', devicePortId(device)); return { inEndpoint, outEndpoint }; }; const initializeRNDIS = (device) => { const interfaceNumber = 0; const iface0 = device.interface(interfaceNumber); iface0.claim(); const iEndpoint = iface0.endpoints[0]; if (!(iEndpoint instanceof usb.InEndpoint)) { throw new Error('endpoint is not an usb.OutEndpoint'); } else { iEndpoint.startPoll(1, 256); } const CONTROL_BUFFER_SIZE = 1025; const message = new messages_1.Message(); const initMsg = message.getRNDISInit(); // RNDIS INIT Message // Windows Control Transfer // https://msdn.microsoft.com/en-us/library/aa447434.aspx // http://www.beyondlogic.org/usbnutshell/usb6.shtml const bmRequestTypeSend = 0x21; // USB_TYPE=CLASS | USB_RECIPIENT=INTERFACE const bmRequestTypeReceive = 0xa1; // USB_DATA=DeviceToHost | USB_TYPE=CLASS | USB_RECIPIENT=INTERFACE // Sending rndis_init_msg (SEND_ENCAPSULATED_COMMAND) device.controlTransfer(bmRequestTypeSend, 0, 0, 0, initMsg, error => { if (error) { throw new Error(`Control transfer error on SEND_ENCAPSULATED ${error}`); } }); // Receive rndis_init_cmplt (GET_ENCAPSULATED_RESPONSE) device.controlTransfer(bmRequestTypeReceive, 0x01, 0, 0, CONTROL_BUFFER_SIZE, error => { if (error) { throw new Error(`Control transfer error on GET_ENCAPSULATED ${error}`); } }); const setMsg = message.getRNDISSet(); // RNDIS SET Message // Send rndis_set_msg (SEND_ENCAPSULATED_COMMAND) device.controlTransfer(bmRequestTypeSend, 0, 0, 0, setMsg, error => { if (error) { throw new Error(`Control transfer error on SEND_ENCAPSULATED ${error}`); } }); // Receive rndis_init_cmplt (GET_ENCAPSULATED_RESPONSE) device.controlTransfer(bmRequestTypeReceive, 0x01, 0, 0, CONTROL_BUFFER_SIZE, error => { if (error) { throw new Error(`Control transfer error on GET_ENCAPSULATED ${error}`); } }); return iEndpoint; }; class UsbBBbootScanner extends events_1.EventEmitter { constructor() { super(); this.usbBBbootDevices = new Map(); this.stepCounter = 0; // We use both events ('attach' and 'detach') and polling getDeviceList() on usb. // We don't know which one will trigger the this.attachDevice call. // So we keep track of attached devices ids in attachedDeviceIds to not run it twice. this.attachedDeviceIds = new Set(); this.boundAttachDevice = this.attachDevice.bind(this); this.boundDetachDevice = this.detachDevice.bind(this); } start() { debug('Waiting for BeagleBone'); // Prepare already connected devices usb.getDeviceList().map(this.boundAttachDevice); // At this point all devices from `usg.getDeviceList()` above // have had an 'attach' event emitted if they were beaglebone. this.emit('ready'); // Watch for new devices being plugged in and prepare them usb.on('attach', this.boundAttachDevice); // Watch for devices detaching usb.on('detach', this.boundDetachDevice); // ts-ignore because of a confusion between NodeJS.Timer and number // @ts-ignore this.interval = setInterval(() => { usb.getDeviceList().forEach(this.boundAttachDevice); }, POLLING_INTERVAL_MS); } stop() { usb.removeListener('attach', this.boundAttachDevice); usb.removeListener('detach', this.boundDetachDevice); clearInterval(this.interval); this.usbBBbootDevices.clear(); } step(device, step) { const usbBBbootDevice = this.getOrCreate(device); usbBBbootDevice.step = step; if (step === UsbBBbootDevice.LAST_STEP) { this.remove(device); } } get(device) { const key = devicePortId(device); return this.usbBBbootDevices.get(key); } getOrCreate(device) { const key = devicePortId(device); let usbBBbootDevice = this.usbBBbootDevices.get(key); if (usbBBbootDevice === undefined) { usbBBbootDevice = new UsbBBbootDevice(key); this.usbBBbootDevices.set(key, usbBBbootDevice); this.emit('attach', usbBBbootDevice); } return usbBBbootDevice; } remove(device) { const key = devicePortId(device); const usbBBbootDevice = this.usbBBbootDevices.get(key); if (usbBBbootDevice !== undefined) { this.usbBBbootDevices.delete(key); this.emit('detach', usbBBbootDevice); } } attachDevice(device) { return __awaiter(this, void 0, void 0, function* () { if (this.attachedDeviceIds.has(getDeviceId(device))) { return; } this.attachedDeviceIds.add(getDeviceId(device)); if (isBeagleBoneInMassStorageMode(device)) { this.step(device, UsbBBbootDevice.LAST_STEP); return; } if (!isUsbBootCapableUSBDevice$(device)) { return; } if (device.deviceDescriptor.iSerialNumber !== 0) { return; } if (exports.isROMUSBDevice(device.deviceDescriptor.idVendor, device.deviceDescriptor.idProduct)) { this.stepCounter = 0; this.process(device, 'u-boot-spl.bin'); } if (exports.isSPLUSBDevice(device.deviceDescriptor.idVendor, device.deviceDescriptor.idProduct)) { setTimeout(() => { this.process(device, 'u-boot.img'); }, 500); } }); } process(device, fileName) { try { device.open(); let rndisInEndpoint; if (platform === 'win32' || platform === 'darwin') { rndisInEndpoint = initializeRNDIS(device); rndisInEndpoint.on('error', (error) => { debug('RNDIS InEndpoint Error', error); }); } const { inEndpoint, outEndpoint } = initializeDevice(device); let serverConfig = {}; serverConfig.bootpFile = fileName; inEndpoint.startPoll(1, 500); // MAXBUFF inEndpoint.on('error', (error) => { debug('InEndpoint Error', error); }); inEndpoint.on('data', (data) => { const message = new messages_1.Message(); const request = message.identify(data); switch (request) { case 'unidentified': break; case 'BOOTP': const { bootPBuff, bootPServerConfig } = message.getBOOTPResponse(data, serverConfig); serverConfig = bootPServerConfig; this.transfer(device, outEndpoint, request, bootPBuff, this.stepCounter++); break; case 'ARP': const { arpBuff, arpServerConfig } = message.getARResponse(data, serverConfig); serverConfig = arpServerConfig; this.transfer(device, outEndpoint, request, arpBuff, this.stepCounter++); break; case 'TFTP': serverConfig = message.getBootFile(data, serverConfig); if (!serverConfig.tftp.fileError) { // tslint:disable-next-line const { tftpBuff, tftpServerConfig } = message.getTFTPData(serverConfig); serverConfig = tftpServerConfig; this.transfer(device, outEndpoint, request, tftpBuff, this.stepCounter++); } else { this.transfer(device, outEndpoint, request, message.getTFTPError(serverConfig), this.stepCounter); } break; case 'TFTP_Data': const { tftpBuff, tftpServerConfig } = message.getTFTPData(serverConfig); serverConfig = tftpServerConfig; if (serverConfig.tftp) { if (serverConfig.tftp.blocks <= serverConfig.tftp.blocks) { this.transfer(device, outEndpoint, request, tftpBuff, this.stepCounter++); } else { if (platform === 'win32' || platform === 'darwin') { rndisInEndpoint.stopPoll(); } inEndpoint.stopPoll(); device.close(); } } break; default: debug('Request', request); } }); } catch (error) { debug('error', error, devicePortId(device)); this.remove(device); } } transfer(device, outEndpoint, request, response, step) { return new Promise((resolve, reject) => { outEndpoint.transfer(response, (cb) => { if (!cb) { if (request === 'BOOTP') { this.step(device, step); } if (request === 'ARP') { this.step(device, step); } if (request === 'TFTP') { this.step(device, step); } if (request === 'TFTP_Data') { this.step(device, step); } } else { debug('Out transfer Error', cb); reject(cb); } }); resolve(true); }); } detachDevice(device) { this.attachedDeviceIds.delete(getDeviceId(device)); if (!isUsbBootCapableUSBDevice$(device)) { return; } setTimeout(() => { const usbBBbootDevice = this.get(device); if (usbBBbootDevice !== undefined && usbBBbootDevice.step === UsbBBbootDevice.LAST_STEP) { debug('device', devicePortId(device), 'did not reattached after', DEVICE_UNPLUG_TIMEOUT, 'ms.'); this.remove(device); } }, DEVICE_UNPLUG_TIMEOUT); } } exports.UsbBBbootScanner = UsbBBbootScanner; // tslint:disable-next-line class UsbBBbootDevice extends events_1.EventEmitter { constructor(portId) { super(); this.portId = portId; this.STEP = 0; } get progress() { return Math.round((this.STEP / UsbBBbootDevice.LAST_STEP) * 100); } get step() { return this.STEP; } set step(step) { this.STEP = step; this.emit('progress', this.progress); } } UsbBBbootDevice.LAST_STEP = 1124; exports.UsbBBbootDevice = UsbBBbootDevice; const devicePortId = (device) => { let result = `${device.busNumber}`; if (device.portNumbers !== undefined) { result += `-${device.portNumbers.join('.')}`; } return result; }; //# sourceMappingURL=index.js.map