Enable NFC for Linux and pcscd on Dell E7470 (and others) with ControlVault2
Jacek Kowalski
2019-03-23 6b0ed423385af7421416094bf09bf3c0cc69ff19
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:
89                 bcm_interface = interface
90                 break
91         if bcm_interface is None:
92             raise Exception('Cannot find vendor-specific interface')
93         logger.debug('Interface found: {}'.format(bcm_interface._str()))
94         
95         logger.debug('Enumerating endpoints...')
96         bulk_in = None
97         bulk_out = None
98         for endpoint in bcm_interface:
99             if endpoint.bmAttributes & usb.util._ENDPOINT_TRANSFER_TYPE_MASK == usb.util.ENDPOINT_TYPE_BULK:
100                 if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_IN:
101                     if bulk_in is not None:
102                         raise Exception('More than one BULK IN endpoint found!')
103                     bulk_in = endpoint
104                     logger.debug('BULK IN found: {}'.format(bulk_in._str()))
105                 if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_OUT:
106                     if bulk_out is not None:
107                         raise Exception('More than one BULK OUT endpoint found!')
108                     bulk_out = endpoint
109                     logger.debug('BULK OUT found: {}'.format(bulk_out._str()))
110         
111         if bulk_in is None:
112             raise Exception('BULK IN endpoint not found!')
113         if bulk_out is None:
114             raise Exception('BULK OUT endpoint not found!')
115         
116         logger.debug('Returning {} object...'.format(cls.__name__))
117         return cls(device, bulk_in, bulk_out)
118
119 turn_on_seq1 = [
120     "10 2f 04 00",
121     "10 2f 1d 03 05 90 65",
122     "10 2f 2d 00",
123     "10 2f 11 01 f7",
124     "01 27 fc 0c 08 00 01 00 01 00 00 00 00 00 00 00",
125 ]
126 turn_on_seq2 = [
127     "10 20 00 01 01",
128     "10 20 01 02 01 00",
129     "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",
130     "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",
131     "10 20 02 06 01 b7 03 02 00 01",
132     "10 2f 06 01 01",
133     "10 20 02 0e 02 51 08 20 79 ff ff ff ff ff ff 58 01 07",
134     "10 21 00 07 02 04 03 02 05 03 03",
135     "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",
136     "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",
137     "10 20 02 10 05 30 01 04 31 01 00 32 01 40 38 01 00 50 01 02",
138     "10 20 02 05 01 00 02 fa 00",
139     "10 20 02 0b 01 c2 08 01 08 00 04 80 c3 c9 01",
140     "10 21 03 0d 06 00 01 01 01 02 01 80 01 82 01 06 01",
141 ]
142
143 def turn_on(communicator):
144     communicator.ctrl_transfer(0x41, 0, 1, 3)
145     communicator.talk(turn_on_seq1)
146     communicator.ctrl_transfer(0x41, 1, 0, 3)
147     communicator.talk(turn_on_seq2)
148     communicator.ctrl_transfer(0x41, 1, 1, 3)
149
150 def turn_off(communicator):
151     communicator.ctrl_transfer(0x41, 1, 0, 3)
152     communicator.ctrl_transfer(0x41, 0, 0, 3)
153
154
155
156 if __name__ == "__main__":
157     if len(sys.argv) < 2:
158         print('Usage: {} [on|off]'.format(sys.argv[0]))
159         sys.exit(2)
160     
161     communicator = BcmCommunicator.find(VENDOR_ID, DEVICE_ID)
162     if sys.argv[1] == 'on':
163         logger.info('Turning NFC on...')
164         turn_on(communicator)
165         logger.info('NFC should be turned on now!')
166     elif sys.argv[1] == 'off':
167         logger.info('Turning NFC off...')
168         turn_off(communicator)
169         logger.info('NFC should be turned off now!')
170     else:
171         raise Exception('Unknown option: {}'.format(sys.argv[1]))