Enable NFC for Linux and pcscd on Dell E7470 (and others) with ControlVault2
Szabolcs Hubai
2019-08-02 6b959a51a468c3bbf5b91890dc90e950393f37da
commit | author | age
6b0ed4 1 #!/usr/bin/env python3
JK 2
3 # Enable NFC on Linux (CCID/PCSCD)
4 # Dell E7470
5 # Dell ControlVault2
6 # BCM20795 (20795A1)
7
8 import binascii
9 import logging
10 import math
11 import struct
12 import sys
13 import time
14 import usb.core
15 import usb.util
16
13e114 17 SUPPORTED_DEVICES = [
JK 18     {'idVendor': 0x0A5C, 'idProduct': 0x5834},
19 ]
6b0ed4 20
JK 21 logging.basicConfig(level=logging.DEBUG)
22 logger = logging.getLogger(__name__)
23
24 def to_hex(val):
25     return ' '.join([bytes([i]).hex() for i in val])
26
27 class BcmCommunicator:
28     def __init__(self, device, bulk_in, bulk_out):
29         self.logger = logging.getLogger(__name__)
30         self.device = device
31         self.bulk_in = bulk_in
32         self.bulk_out = bulk_out
33     
34     def ctrl_transfer(self, *args, **kwargs):
35         self.logger.debug('Control: {} {}'.format(args, kwargs))
36         return self.device.ctrl_transfer(*args, **kwargs)
37     
38     def write(self, *args, **kwargs):
39         return self.bulk_out.write(*args, **kwargs)
40     
41     def read(self, *args, **kwargs):
42         return self.bulk_in.read(*args, **kwargs)
43     
44     def send_packet(self, payload):
45         packet_type = 0x01
46         unknown1 = 0x00
47         length = len(payload)
48         
49         packet = struct.pack('>BBH', packet_type, unknown1, length) + payload
50         
51         self.logger.debug('Put: {}'.format(to_hex(packet)))
52         self.write(packet)
53
54     def recv_packet(self):
55         packet = self.read(64, timeout=5000).tobytes()
56         tag = packet[0:2]
57         if tag != b'\x00\x00':
58             raise Exception('Unknown tag: {}'.format(tag.hex()))
59         length = packet[2:4]
60         length = struct.unpack('>H', length)[0]
61         
62         for i in range(0, math.ceil(length/64) - 1):
63             packet += self.read(64).tobytes()
64         
65         self.logger.debug('Got: {}'.format(to_hex(packet)))
66         return packet[4:]
67     
68     def talk(self, exchange):
69         for packet in exchange:
70             self.send_packet(bytes.fromhex(packet))
71             data = self.recv_packet()
72             
73             if data[1] == 0x61:
74                 packet = self.recv_packet()
75     
13e114 76     @staticmethod
JK 77     def _dev_match(template, candidate):
78         for prop, value in template.items():
79             if prop not in candidate.__dict__ or candidate.__dict__[prop] != value:
80                 return False
81         return True
82     
6b0ed4 83     @classmethod
13e114 84     def _dev_matcher(cls, dev):
JK 85         for device in SUPPORTED_DEVICES:
86             if cls._dev_match(device, dev):
87                 return True
88         return False
89     
90     @classmethod
91     def find(cls):
6b0ed4 92         logger = logging.getLogger(__name__)
13e114 93         logger.info('Looking for BCM device...')
6b0ed4 94         
13e114 95         device = usb.core.find(custom_match=cls._dev_matcher)
6b0ed4 96         if device is None:
13e114 97             raise Exception('Cannot find BCM device - check list of supported devices')
JK 98         logger.info('Found {:04X}:{:04X}'.format(device.idVendor, device.idProduct))
6b0ed4 99         
JK 100         logger.debug('Enumerating interfaces...')
101         configuration = device.get_active_configuration()
102         bcm_interface = None
103         for interface in configuration:
104             if interface.bInterfaceClass == 0xff and interface.iInterface == 0x08:
36fd63 105                 if bcm_interface is not None:
JK 106                     raise Exception('More than one vendor-specific interface found!')
6b0ed4 107                 bcm_interface = interface
JK 108         if bcm_interface is None:
109             raise Exception('Cannot find vendor-specific interface')
110         logger.debug('Interface found: {}'.format(bcm_interface._str()))
111         
112         logger.debug('Enumerating endpoints...')
113         bulk_in = None
114         bulk_out = None
115         for endpoint in bcm_interface:
116             if endpoint.bmAttributes & usb.util._ENDPOINT_TRANSFER_TYPE_MASK == usb.util.ENDPOINT_TYPE_BULK:
117                 if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_IN:
118                     if bulk_in is not None:
119                         raise Exception('More than one BULK IN endpoint found!')
120                     bulk_in = endpoint
121                     logger.debug('BULK IN found: {}'.format(bulk_in._str()))
122                 if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_OUT:
123                     if bulk_out is not None:
124                         raise Exception('More than one BULK OUT endpoint found!')
125                     bulk_out = endpoint
126                     logger.debug('BULK OUT found: {}'.format(bulk_out._str()))
127         
128         if bulk_in is None:
129             raise Exception('BULK IN endpoint not found!')
130         if bulk_out is None:
131             raise Exception('BULK OUT endpoint not found!')
132         
133         logger.debug('Returning {} object...'.format(cls.__name__))
134         return cls(device, bulk_in, bulk_out)
135
136 turn_on_seq1 = [
137     "10 2f 04 00",
138     "10 2f 1d 03 05 90 65",
139     "10 2f 2d 00",
140     "10 2f 11 01 f7",
141     "01 27 fc 0c 08 00 01 00 01 00 00 00 00 00 00 00",
142 ]
143 turn_on_seq2 = [
144     "10 20 00 01 01",
145     "10 20 01 02 01 00",
146     "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",
147     "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",
148     "10 20 02 06 01 b7 03 02 00 01",
149     "10 2f 06 01 01",
150     "10 20 02 0e 02 51 08 20 79 ff ff ff ff ff ff 58 01 07",
151     "10 21 00 07 02 04 03 02 05 03 03",
152     "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",
153     "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",
154     "10 20 02 10 05 30 01 04 31 01 00 32 01 40 38 01 00 50 01 02",
155     "10 20 02 05 01 00 02 fa 00",
156     "10 20 02 0b 01 c2 08 01 08 00 04 80 c3 c9 01",
157     "10 21 03 0d 06 00 01 01 01 02 01 80 01 82 01 06 01",
158 ]
159
160 def turn_on(communicator):
161     communicator.ctrl_transfer(0x41, 0, 1, 3)
162     communicator.talk(turn_on_seq1)
163     communicator.ctrl_transfer(0x41, 1, 0, 3)
164     communicator.talk(turn_on_seq2)
165     communicator.ctrl_transfer(0x41, 1, 1, 3)
166
167 def turn_off(communicator):
168     communicator.ctrl_transfer(0x41, 1, 0, 3)
169     communicator.ctrl_transfer(0x41, 0, 0, 3)
170
171
172
173 if __name__ == "__main__":
174     if len(sys.argv) < 2:
175         print('Usage: {} [on|off]'.format(sys.argv[0]))
176         sys.exit(2)
177     
13e114 178     communicator = BcmCommunicator.find()
6b0ed4 179     if sys.argv[1] == 'on':
JK 180         logger.info('Turning NFC on...')
181         turn_on(communicator)
182         logger.info('NFC should be turned on now!')
183     elif sys.argv[1] == 'off':
184         logger.info('Turning NFC off...')
185         turn_off(communicator)
186         logger.info('NFC should be turned off now!')
187     else:
188         raise Exception('Unknown option: {}'.format(sys.argv[1]))