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