summaryrefslogtreecommitdiff
path: root/tools/hciattach_bcm43xx.c
blob: b89fc1b505f6722a3c9039e61827b4cefc8f4fb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2014 Intel Corporation. All rights reserved.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <time.h>
#include <limits.h>

#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"

#include "hciattach.h"

#define FW_EXT ".hcd"

#define BCM43XX_CLOCK_48 1
#define BCM43XX_CLOCK_24 2

#define CMD_SUCCESS 0x00

#define CC_MIN_SIZE 7

#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))

static int bcm43xx_read_local_name(int fd, char *name, size_t size)
{
	unsigned char cmd[] = { HCI_COMMAND_PKT, 0x14, 0x0C, 0x00 };
	unsigned char *resp;
	unsigned int name_len;

	resp = malloc(size + CC_MIN_SIZE);
	if (!resp)
		return -1;

	tcflush(fd, TCIOFLUSH);

	if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
		fprintf(stderr, "Failed to write read local name command\n");
		goto fail;
	}

	if (read_hci_event(fd, resp, size) < CC_MIN_SIZE) {
		fprintf(stderr, "Failed to read local name, invalid HCI event\n");
		goto fail;
	}

	if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) {
		fprintf(stderr, "Failed to read local name, command failure\n");
		goto fail;
	}

	name_len = resp[2] - 1;

	strncpy(name, (char *) &resp[7], MIN(name_len, size));
	name[size - 1] = 0;

	free(resp);
	return 0;

fail:
	free(resp);
	return -1;
}

static int bcm43xx_reset(int fd)
{
	unsigned char cmd[] = { HCI_COMMAND_PKT, 0x03, 0x0C, 0x00 };
	unsigned char resp[CC_MIN_SIZE];

	if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
		fprintf(stderr, "Failed to write reset command\n");
		return -1;
	}

	if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) {
		fprintf(stderr, "Failed to reset chip, invalid HCI event\n");
		return -1;
	}

	if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) {
		fprintf(stderr, "Failed to reset chip, command failure\n");
		return -1;
	}

	return 0;
}

static int bcm43xx_set_bdaddr(int fd, const char *bdaddr)
{
	unsigned char cmd[] =
		{ HCI_COMMAND_PKT, 0x01, 0xfc, 0x06, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00 };
	unsigned char resp[CC_MIN_SIZE];

	printf("Set BDADDR UART: %s\n", bdaddr);

	if (str2ba(bdaddr, (bdaddr_t *) (&cmd[4])) < 0) {
		fprintf(stderr, "Incorrect bdaddr\n");
		return -1;
	}

	tcflush(fd, TCIOFLUSH);

	if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
		fprintf(stderr, "Failed to write set bdaddr command\n");
		return -1;
	}

	if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) {
		fprintf(stderr, "Failed to set bdaddr, invalid HCI event\n");
		return -1;
	}

	if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) {
		fprintf(stderr, "Failed to set bdaddr, command failure\n");
		return -1;
	}

	return 0;
}

static int bcm43xx_set_clock(int fd, unsigned char clock)
{
	unsigned char cmd[] = { HCI_COMMAND_PKT, 0x45, 0xfc, 0x01, 0x00 };
	unsigned char resp[CC_MIN_SIZE];

	printf("Set Controller clock (%d)\n", clock);

	cmd[4] = clock;

	tcflush(fd, TCIOFLUSH);

	if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
		fprintf(stderr, "Failed to write update clock command\n");
		return -1;
	}

	if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) {
		fprintf(stderr, "Failed to update clock, invalid HCI event\n");
		return -1;
	}

	if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) {
		fprintf(stderr, "Failed to update clock, command failure\n");
		return -1;
	}

	return 0;
}

static int bcm43xx_set_speed(int fd, struct termios *ti, uint32_t speed)
{
	unsigned char cmd[] =
		{ HCI_COMMAND_PKT, 0x18, 0xfc, 0x06, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00 };
	unsigned char resp[CC_MIN_SIZE];

	if (speed > 3000000 && bcm43xx_set_clock(fd, BCM43XX_CLOCK_48))
		return -1;

	printf("Set Controller UART speed to %d bit/s\n", speed);

	cmd[6] = (uint8_t) (speed);
	cmd[7] = (uint8_t) (speed >> 8);
	cmd[8] = (uint8_t) (speed >> 16);
	cmd[9] = (uint8_t) (speed >> 24);

	tcflush(fd, TCIOFLUSH);

	if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
		fprintf(stderr, "Failed to write update baudrate command\n");
		return -1;
	}

	if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) {
		fprintf(stderr, "Failed to update baudrate, invalid HCI event\n");
		return -1;
	}

	if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) {
		fprintf(stderr, "Failed to update baudrate, command failure\n");
		return -1;
	}

	if (set_speed(fd, ti, speed) < 0) {
		perror("Can't set host baud rate");
		return -1;
	}

	return 0;
}

static int bcm43xx_load_firmware(int fd, const char *fw)
{
	unsigned char cmd[] = { HCI_COMMAND_PKT, 0x2e, 0xfc, 0x00 };
	struct timespec tm_mode = { 0, 50000000 };
	struct timespec tm_ready = { 0, 200000000 };
	unsigned char resp[CC_MIN_SIZE];
	unsigned char tx_buf[1024];
	int len, fd_fw, n;

	printf("Flash firmware %s\n", fw);

	fd_fw = open(fw, O_RDONLY);
	if (fd_fw < 0) {
		fprintf(stderr, "Unable to open firmware (%s)\n", fw);
		return -1;
	}

	tcflush(fd, TCIOFLUSH);

	if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
		fprintf(stderr, "Failed to write download mode command\n");
		goto fail;
	}

	if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) {
		fprintf(stderr, "Failed to load firmware, invalid HCI event\n");
		goto fail;
	}

	if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) {
		fprintf(stderr, "Failed to load firmware, command failure\n");
		goto fail;
	}

	/* Wait 50ms to let the firmware placed in download mode */
	nanosleep(&tm_mode, NULL);

	tcflush(fd, TCIOFLUSH);

	while ((n = read(fd_fw, &tx_buf[1], 3))) {
		if (n < 0) {
			fprintf(stderr, "Failed to read firmware\n");
			goto fail;
		}

		tx_buf[0] = HCI_COMMAND_PKT;

		len = tx_buf[3];

		if (read(fd_fw, &tx_buf[4], len) < 0) {
			fprintf(stderr, "Failed to read firmware\n");
			goto fail;
		}

		if (write(fd, tx_buf, len + 4) != (len + 4)) {
			fprintf(stderr, "Failed to write firmware\n");
			goto fail;
		}

		read_hci_event(fd, resp, sizeof(resp));
		tcflush(fd, TCIOFLUSH);
	}

	/* Wait for firmware ready */
	nanosleep(&tm_ready, NULL);

	close(fd_fw);
	return 0;

fail:
	close(fd_fw);
	return -1;
}

static int bcm43xx_locate_patch(const char *dir_name,
		const char *chip_name, char *location)
{
	DIR *dir;
	int ret = -1;

	dir = opendir(dir_name);
	if (!dir) {
		fprintf(stderr, "Cannot open directory '%s': %s\n",
				dir_name, strerror(errno));
		return -1;
	}

	/* Recursively look for a BCM43XX*.hcd */
	while (1) {
		struct dirent *entry = readdir(dir);
		if (!entry)
			break;

		if (entry->d_type & DT_DIR) {
			char path[PATH_MAX];

			if (!strcmp(entry->d_name, "..") || !strcmp(entry->d_name, "."))
				continue;

			snprintf(path, PATH_MAX, "%s/%s", dir_name, entry->d_name);

			ret = bcm43xx_locate_patch(path, chip_name, location);
			if (!ret)
				break;
		} else if (!strncmp(chip_name, entry->d_name, strlen(chip_name))) {
			unsigned int name_len = strlen(entry->d_name);
			size_t curs_ext = name_len - sizeof(FW_EXT) + 1;

			if (curs_ext > name_len)
				break;

			if (strncmp(FW_EXT, &entry->d_name[curs_ext], sizeof(FW_EXT)))
				break;

			/* found */
			snprintf(location, PATH_MAX, "%s/%s", dir_name, entry->d_name);
			ret = 0;
			break;
		}
	}

	closedir(dir);

	return ret;
}

int bcm43xx_init(int fd, int def_speed, int speed, struct termios *ti,
		const char *bdaddr)
{
	char chip_name[20];
	char fw_path[PATH_MAX];

	printf("bcm43xx_init\n");

	if (bcm43xx_reset(fd))
		return -1;

	if (bcm43xx_read_local_name(fd, chip_name, sizeof(chip_name)))
		return -1;

	if (bcm43xx_locate_patch(FIRMWARE_DIR, chip_name, fw_path)) {
		fprintf(stderr, "Patch not found, continue anyway\n");
	} else {
		if (bcm43xx_set_speed(fd, ti, speed))
			return -1;

		if (bcm43xx_load_firmware(fd, fw_path))
			return -1;

		/* Controller speed has been reset to def speed */
		if (set_speed(fd, ti, def_speed) < 0) {
			perror("Can't set host baud rate");
			return -1;
		}

		if (bcm43xx_reset(fd))
			return -1;
	}

	if (bdaddr)
		bcm43xx_set_bdaddr(fd, bdaddr);

	if (bcm43xx_set_speed(fd, ti, speed))
		return -1;

	return 0;
}