#!/usr/bin/env python3
|
|
# Enable NFC on Linux (CCID/PCSCD)
|
# Dell E7470
|
# Dell ControlVault2
|
# BCM20795 (20795A1)
|
|
import binascii
|
import logging
|
import math
|
import struct
|
import sys
|
import time
|
import usb.core
|
import usb.util
|
|
SUPPORTED_DEVICES = [
|
{'idVendor': 0x0A5C, 'idProduct': 0x5834},
|
{'idVendor': 0x0A5C, 'idProduct': 0x5832},
|
]
|
|
logging.basicConfig(level=logging.DEBUG)
|
logger = logging.getLogger(__name__)
|
|
def to_hex(val):
|
return ' '.join([bytes([i]).hex() for i in val])
|
|
class BcmCommunicator:
|
def __init__(self, device, bulk_in, bulk_out):
|
self.logger = logging.getLogger(__name__)
|
self.device = device
|
self.bulk_in = bulk_in
|
self.bulk_out = bulk_out
|
|
def ctrl_transfer(self, *args, **kwargs):
|
self.logger.debug('Control: {} {}'.format(args, kwargs))
|
return self.device.ctrl_transfer(*args, **kwargs)
|
|
def write(self, *args, **kwargs):
|
return self.bulk_out.write(*args, **kwargs)
|
|
def read(self, *args, **kwargs):
|
return self.bulk_in.read(*args, **kwargs)
|
|
def send_packet(self, payload):
|
packet_type = 0x01
|
unknown1 = 0x00
|
length = len(payload)
|
|
packet = struct.pack('>BBH', packet_type, unknown1, length) + payload
|
|
self.logger.debug('Put: {}'.format(to_hex(packet)))
|
self.write(packet)
|
|
def recv_packet(self):
|
packet = self.read(64, timeout=5000).tobytes()
|
tag = packet[0:2]
|
if tag != b'\x00\x00':
|
raise Exception('Unknown tag: {}'.format(tag.hex()))
|
length = packet[2:4]
|
length = struct.unpack('>H', length)[0]
|
|
for i in range(0, math.ceil(length/64) - 1):
|
packet += self.read(64).tobytes()
|
|
self.logger.debug('Got: {}'.format(to_hex(packet)))
|
return packet[4:]
|
|
def talk(self, exchange):
|
for packet in exchange:
|
self.send_packet(bytes.fromhex(packet))
|
data = self.recv_packet()
|
|
if data[1] == 0x61:
|
packet = self.recv_packet()
|
|
@staticmethod
|
def _dev_match(template, candidate):
|
for prop, value in template.items():
|
if prop not in candidate.__dict__ or candidate.__dict__[prop] != value:
|
return False
|
return True
|
|
@classmethod
|
def _dev_matcher(cls, dev):
|
for device in SUPPORTED_DEVICES:
|
if cls._dev_match(device, dev):
|
return True
|
return False
|
|
@classmethod
|
def find(cls):
|
logger = logging.getLogger(__name__)
|
logger.info('Looking for BCM device...')
|
|
device = usb.core.find(custom_match=cls._dev_matcher)
|
if device is None:
|
raise Exception('Cannot find BCM device - check list of supported devices')
|
logger.info('Found {:04X}:{:04X}'.format(device.idVendor, device.idProduct))
|
|
logger.debug('Enumerating interfaces...')
|
configuration = device.get_active_configuration()
|
bcm_interface = None
|
for interface in configuration:
|
if interface.bInterfaceClass == 0xff and interface.iInterface == 0x08:
|
if bcm_interface is not None:
|
raise Exception('More than one vendor-specific interface found!')
|
bcm_interface = interface
|
if bcm_interface is None:
|
raise Exception('Cannot find vendor-specific interface')
|
logger.debug('Interface found: {}'.format(bcm_interface._str()))
|
|
logger.debug('Enumerating endpoints...')
|
bulk_in = None
|
bulk_out = None
|
for endpoint in bcm_interface:
|
if endpoint.bmAttributes & usb.util._ENDPOINT_TRANSFER_TYPE_MASK == usb.util.ENDPOINT_TYPE_BULK:
|
if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_IN:
|
if bulk_in is not None:
|
raise Exception('More than one BULK IN endpoint found!')
|
bulk_in = endpoint
|
logger.debug('BULK IN found: {}'.format(bulk_in._str()))
|
if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_OUT:
|
if bulk_out is not None:
|
raise Exception('More than one BULK OUT endpoint found!')
|
bulk_out = endpoint
|
logger.debug('BULK OUT found: {}'.format(bulk_out._str()))
|
|
if bulk_in is None:
|
raise Exception('BULK IN endpoint not found!')
|
if bulk_out is None:
|
raise Exception('BULK OUT endpoint not found!')
|
|
logger.debug('Returning {} object...'.format(cls.__name__))
|
return cls(device, bulk_in, bulk_out)
|
|
turn_on_seq1 = [
|
"10 2f 04 00",
|
"10 2f 1d 03 05 90 65",
|
"10 2f 2d 00",
|
"10 2f 11 01 f7",
|
"01 27 fc 0c 08 00 01 00 01 00 00 00 00 00 00 00",
|
]
|
turn_on_seq2 = [
|
"10 20 00 01 01",
|
"10 20 01 02 01 00",
|
"10 20 02 67 01 b9 64 01 00 ff ff 50 00 8b 13 00 10 00 06 00 00 00 00 00 ff 00 00 00 ff 00 00 04 00 00 00 00 03 00 00 00 03 00 0c 00 00 0d 00 00 00 00 00 00 00 00 00 00 33 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 53 3b 0f 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00",
|
"10 20 02 90 0a ca 05 00 00 00 00 2c 80 01 01 b0 05 01 03 03 03 08 b5 03 01 03 ff c9 0d 24 00 00 00 01 00 bb 00 e4 00 0a 01 02 d6 0d 01 02 00 00 00 00 00 01 00 01 5a 00 8a b2 02 e8 03 c8 1e 06 1f 00 0a 00 30 00 04 24 00 1c 00 75 00 77 00 76 00 1c 00 03 00 0a 00 56 01 00 00 40 04 d7 01 07 dd 32 00 00 00 29 16 08 08 06 04 00 00 00 1f 27 0a 6d 20 00 52 20 00 00 00 01 85 00 00 32 1f 00 00 02 0a 16 00 02 55 55 55 55 55 55 55 55 55 55 55 55 55 1e",
|
"10 20 02 06 01 b7 03 02 00 01",
|
"10 2f 06 01 01",
|
"10 20 02 0e 02 51 08 20 79 ff ff ff ff ff ff 58 01 07",
|
"10 21 00 07 02 04 03 02 05 03 03",
|
"10 20 02 17 01 29 14 46 66 6d 01 01 11 02 02 07 ff 03 02 00 13 04 01 64 07 01 03",
|
"10 20 02 1a 02 61 14 46 66 6d 01 01 11 02 02 07 ff 03 02 00 13 04 01 64 07 01 03 60 01 07",
|
"10 20 02 10 05 30 01 04 31 01 00 32 01 40 38 01 00 50 01 02",
|
"10 20 02 05 01 00 02 fa 00",
|
"10 20 02 0b 01 c2 08 01 08 00 04 80 c3 c9 01",
|
"10 21 03 0d 06 00 01 01 01 02 01 80 01 82 01 06 01",
|
]
|
|
def turn_on(communicator):
|
communicator.ctrl_transfer(0x41, 0, 1, 3)
|
communicator.talk(turn_on_seq1)
|
communicator.ctrl_transfer(0x41, 1, 0, 3)
|
communicator.talk(turn_on_seq2)
|
communicator.ctrl_transfer(0x41, 1, 1, 3)
|
|
def turn_off(communicator):
|
communicator.ctrl_transfer(0x41, 1, 0, 3)
|
communicator.ctrl_transfer(0x41, 0, 0, 3)
|
|
|
|
if __name__ == "__main__":
|
if len(sys.argv) < 2:
|
print('Usage: {} [on|off]'.format(sys.argv[0]))
|
sys.exit(2)
|
|
communicator = BcmCommunicator.find()
|
if sys.argv[1] == 'on':
|
logger.info('Turning NFC on...')
|
turn_on(communicator)
|
logger.info('NFC should be turned on now!')
|
elif sys.argv[1] == 'off':
|
logger.info('Turning NFC off...')
|
turn_off(communicator)
|
logger.info('NFC should be turned off now!')
|
else:
|
raise Exception('Unknown option: {}'.format(sys.argv[1]))
|