* [PATCHv4 4/8] android/ipc-tester: Add daemon shutdown handler
From: Jakub Tyszkowski @ 2014-01-16 8:38 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <1389861512-2326-1-git-send-email-jakub.tyszkowski@tieto.com>
Handle daemon shutdown asynchronously.
---
android/ipc-tester.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/android/ipc-tester.c b/android/ipc-tester.c
index b76be32..6ba6e1f 100644
--- a/android/ipc-tester.c
+++ b/android/ipc-tester.c
@@ -52,6 +52,7 @@ struct test_data {
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
pid_t bluetoothd_pid;
+ bool setup_done;
};
#define CONNECT_TIMEOUT (5 * 1000)
@@ -359,6 +360,30 @@ static void cleanup_ipc(void)
cmd_sk = -1;
}
+static gboolean check_for_daemon(gpointer user_data)
+{
+ int status;
+ struct test_data *data = user_data;
+
+ if ((waitpid(data->bluetoothd_pid, &status, WNOHANG))
+ != data->bluetoothd_pid)
+ return true;
+
+ if (data->setup_done) {
+ if (WIFEXITED(status) &&
+ (WEXITSTATUS(status) == EXIT_SUCCESS)) {
+ tester_test_passed();
+ return false;
+ }
+ tester_test_failed();
+ } else {
+ tester_setup_failed();
+ }
+
+ tester_warn("Unexpected Daemon shutdown with status %d", status);
+ return false;
+}
+
static void setup(const void *data)
{
struct test_data *test_data = tester_get_data();
@@ -399,6 +424,10 @@ static void setup(const void *data)
tester_setup_failed();
return;
}
+
+ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, check_for_daemon, test_data,
+ NULL);
+
if (!init_ipc()) {
tester_warn("Cannot initialize IPC mechanism!");
tester_setup_failed();
@@ -406,6 +435,8 @@ static void setup(const void *data)
}
/* TODO: register modules */
+
+ test_data->setup_done = true;
}
static void teardown(const void *data)
--
1.8.5.2
^ permalink raw reply related
* [PATCHv4 3/8] android/ipc-tester: Add IPC initialization
From: Jakub Tyszkowski @ 2014-01-16 8:38 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <1389861512-2326-1-git-send-email-jakub.tyszkowski@tieto.com>
This patch adds IPC mechanism initialization.
The deamon is being started and IPC socket connection is established.
---
android/ipc-tester.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/android/ipc-tester.c b/android/ipc-tester.c
index d920ccf..b76be32 100644
--- a/android/ipc-tester.c
+++ b/android/ipc-tester.c
@@ -23,6 +23,8 @@
#include <stdlib.h>
#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
@@ -38,6 +40,8 @@
#include "src/shared/mgmt.h"
#include "src/shared/hciemu.h"
+#include "hal-msg.h"
+#include <cutils/properties.h>
#define WAIT_FOR_SIGNAL_TIME 2 /* in seconds */
#define EMULATOR_SIGNAL "emulator_started"
@@ -50,8 +54,14 @@ struct test_data {
pid_t bluetoothd_pid;
};
+#define CONNECT_TIMEOUT (5 * 1000)
+#define SERVICE_NAME "bluetoothd"
+
static char exec_dir[PATH_MAX + 1];
+static int cmd_sk = -1;
+static int notif_sk = -1;
+
static void read_info_callback(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
@@ -246,6 +256,109 @@ failed:
close(fd);
}
+static int accept_connection(int sk)
+{
+ int err;
+ struct pollfd pfd;
+ int new_sk;
+
+ memset(&pfd, 0 , sizeof(pfd));
+ pfd.fd = sk;
+ pfd.events = POLLIN;
+
+ err = poll(&pfd, 1, CONNECT_TIMEOUT);
+ if (err < 0) {
+ err = errno;
+ tester_warn("Failed to poll: %d (%s)", err, strerror(err));
+ return -errno;
+ }
+
+ if (err == 0) {
+ tester_warn("bluetoothd connect timeout");
+ return -errno;
+ }
+
+ new_sk = accept(sk, NULL, NULL);
+ if (new_sk < 0) {
+ err = errno;
+ tester_warn("Failed to accept socket: %d (%s)",
+ err, strerror(err));
+ return -errno;
+ }
+
+ return new_sk;
+}
+
+static bool init_ipc(void)
+{
+ struct sockaddr_un addr;
+
+ int sk;
+ int err;
+
+ sk = socket(AF_LOCAL, SOCK_SEQPACKET, 0);
+ if (sk < 0) {
+ err = errno;
+ tester_warn("Failed to create socket: %d (%s)", err,
+ strerror(err));
+ return false;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+
+ memcpy(addr.sun_path, BLUEZ_HAL_SK_PATH, sizeof(BLUEZ_HAL_SK_PATH));
+
+ if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ err = errno;
+ tester_warn("Failed to bind socket: %d (%s)", err,
+ strerror(err));
+ close(sk);
+ return false;
+ }
+
+ if (listen(sk, 2) < 0) {
+ err = errno;
+ tester_warn("Failed to listen on socket: %d (%s)", err,
+ strerror(err));
+ close(sk);
+ return false;
+ }
+
+ /* Start Android Bluetooth daemon service */
+ if (property_set("ctl.start", SERVICE_NAME) < 0) {
+ tester_warn("Failed to start service %s", SERVICE_NAME);
+ close(sk);
+ return false;
+ }
+
+ cmd_sk = accept_connection(sk);
+ if (cmd_sk < 0) {
+ close(sk);
+ return false;
+ }
+
+ notif_sk = accept_connection(sk);
+ if (notif_sk < 0) {
+ close(sk);
+ close(cmd_sk);
+ cmd_sk = -1;
+ return false;
+ }
+
+ tester_print("bluetoothd connected");
+
+ close(sk);
+
+ return true;
+}
+
+static void cleanup_ipc(void)
+{
+ close(cmd_sk);
+ cmd_sk = -1;
+}
+
static void setup(const void *data)
{
struct test_data *test_data = tester_get_data();
@@ -286,12 +399,21 @@ static void setup(const void *data)
tester_setup_failed();
return;
}
+ if (!init_ipc()) {
+ tester_warn("Cannot initialize IPC mechanism!");
+ tester_setup_failed();
+ return;
+ }
+
+ /* TODO: register modules */
}
static void teardown(const void *data)
{
struct test_data *test_data = tester_get_data();
+ cleanup_ipc();
+
if (test_data->bluetoothd_pid)
waitpid(test_data->bluetoothd_pid, NULL, 0);
--
1.8.5.2
^ permalink raw reply related
* [PATCHv4 2/8] android/ipc-tester: Run daemon in separate process
From: Jakub Tyszkowski @ 2014-01-16 8:38 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <1389861512-2326-1-git-send-email-jakub.tyszkowski@tieto.com>
This patch adds new process waiting to run daemon when needed.
---
android/ipc-tester.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 123 insertions(+), 1 deletion(-)
diff --git a/android/ipc-tester.c b/android/ipc-tester.c
index 9a1b4b0..d920ccf 100644
--- a/android/ipc-tester.c
+++ b/android/ipc-tester.c
@@ -21,8 +21,14 @@
*
*/
+#include <stdlib.h>
#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+#include <libgen.h>
#include <glib.h>
#include "lib/bluetooth.h"
@@ -33,13 +39,19 @@
#include "src/shared/hciemu.h"
+#define WAIT_FOR_SIGNAL_TIME 2 /* in seconds */
+#define EMULATOR_SIGNAL "emulator_started"
+
struct test_data {
struct mgmt *mgmt;
uint16_t mgmt_index;
struct hciemu *hciemu;
enum hciemu_type hciemu_type;
+ pid_t bluetoothd_pid;
};
+static char exec_dir[PATH_MAX + 1];
+
static void read_info_callback(uint8_t status, uint16_t length,
const void *param, void *user_data)
{
@@ -168,13 +180,121 @@ static void test_post_teardown(const void *data)
test_data->hciemu = NULL;
}
+static void bluetoothd_start(int hci_index)
+{
+ char prg_name[PATH_MAX + 1];
+ char index[8];
+ char *prg_argv[4];
+
+ snprintf(prg_name, sizeof(prg_name), "%s/%s", exec_dir, "bluetoothd");
+ snprintf(index, sizeof(index), "%d", hci_index);
+
+ prg_argv[0] = prg_name;
+ prg_argv[1] = "-i";
+ prg_argv[2] = index;
+ prg_argv[3] = NULL;
+
+ if (!tester_use_debug())
+ fclose(stderr);
+
+ execve(prg_argv[0], prg_argv, NULL);
+}
+
+static void emulator(int pipe, int hci_index)
+{
+ static const char SYSTEM_SOCKET_PATH[] = "\0android_system";
+ char buf[1024];
+ struct sockaddr_un addr;
+ struct timeval tv;
+ int fd;
+ ssize_t len;
+
+ fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ goto failed;
+
+ tv.tv_sec = WAIT_FOR_SIGNAL_TIME;
+ tv.tv_usec = 0;
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ memcpy(addr.sun_path, SYSTEM_SOCKET_PATH, sizeof(SYSTEM_SOCKET_PATH));
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ perror("Failed to bind system socket");
+ goto failed;
+ }
+
+ len = write(pipe, EMULATOR_SIGNAL, sizeof(EMULATOR_SIGNAL));
+
+ if (len != sizeof(EMULATOR_SIGNAL))
+ goto failed;
+
+ memset(buf, 0, sizeof(buf));
+
+ len = read(fd, buf, sizeof(buf));
+ if (len <= 0 || (strcmp(buf, "ctl.start=bluetoothd")))
+ goto failed;
+
+ close(pipe);
+ close(fd);
+ bluetoothd_start(hci_index);
+
+failed:
+ close(pipe);
+ close(fd);
+}
+
static void setup(const void *data)
{
- tester_setup_failed();
+ struct test_data *test_data = tester_get_data();
+ int signal_fd[2];
+ char buf[1024];
+ pid_t pid;
+ int len;
+
+ if (pipe(signal_fd)) {
+ tester_setup_failed();
+ return;
+ }
+
+ pid = fork();
+
+ if (pid < 0) {
+ close(signal_fd[0]);
+ close(signal_fd[1]);
+ tester_setup_failed();
+ return;
+ }
+
+ if (pid == 0) {
+ if (!tester_use_debug())
+ fclose(stderr);
+
+ close(signal_fd[0]);
+ emulator(signal_fd[1], test_data->mgmt_index);
+ exit(0);
+ }
+
+ close(signal_fd[1]);
+ test_data->bluetoothd_pid = pid;
+
+ len = read(signal_fd[0], buf, sizeof(buf));
+ if (len <= 0 || (strcmp(buf, EMULATOR_SIGNAL))) {
+ close(signal_fd[0]);
+ tester_setup_failed();
+ return;
+ }
}
static void teardown(const void *data)
{
+ struct test_data *test_data = tester_get_data();
+
+ if (test_data->bluetoothd_pid)
+ waitpid(test_data->bluetoothd_pid, NULL, 0);
+
tester_teardown_complete();
}
@@ -196,6 +316,8 @@ static void ipc_send_tc(const void *data)
int main(int argc, char *argv[])
{
+ snprintf(exec_dir, sizeof(exec_dir), "%s", dirname(argv[0]));
+
tester_init(&argc, &argv);
test_bredrle("Test Dummy", NULL, setup, ipc_send_tc, teardown);
--
1.8.5.2
^ permalink raw reply related
* [PATCHv4 1/8] android/ipc-tester: Skeleton for ipc negative tester
From: Jakub Tyszkowski @ 2014-01-16 8:38 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <1389861512-2326-1-git-send-email-jakub.tyszkowski@tieto.com>
Add skeleton for ipc negative testing.
---
.gitignore | 1 +
android/Makefile.am | 17 +++++
android/ipc-tester.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 222 insertions(+)
create mode 100644 android/ipc-tester.c
diff --git a/.gitignore b/.gitignore
index ac76fe2..73a68e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,4 +113,5 @@ android/system-emulator
android/bluetoothd
android/haltest
android/android-tester
+android/ipc-tester
android/bluetoothd-snoop
diff --git a/android/Makefile.am b/android/Makefile.am
index cd4a526..fb632d4 100644
--- a/android/Makefile.am
+++ b/android/Makefile.am
@@ -119,6 +119,23 @@ android_android_tester_LDFLAGS = -pthread -ldl
plugin_LTLIBRARIES += android/audio.a2dp.default.la
+noinst_PROGRAMS += android/ipc-tester
+
+android_ipc_tester_SOURCES = emulator/btdev.h emulator/btdev.c \
+ emulator/bthost.h emulator/bthost.c \
+ src/shared/io.h src/shared/io-glib.c \
+ src/shared/queue.h src/shared/queue.c \
+ src/shared/util.h src/shared/util.c \
+ src/shared/mgmt.h src/shared/mgmt.c \
+ src/shared/hciemu.h src/shared/hciemu.c \
+ src/shared/tester.h src/shared/tester.c \
+ android/hal-utils.h android/hal-utils.c \
+ android/ipc-tester.c
+
+android_ipc_tester_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/android
+
+android_ipc_tester_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@
+
android_audio_a2dp_default_la_SOURCES = android/audio-msg.h \
android/hal-audio.c \
android/hardware/audio.h \
diff --git a/android/ipc-tester.c b/android/ipc-tester.c
new file mode 100644
index 0000000..9a1b4b0
--- /dev/null
+++ b/android/ipc-tester.c
@@ -0,0 +1,204 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <unistd.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/mgmt.h"
+
+#include "src/shared/tester.h"
+#include "src/shared/mgmt.h"
+#include "src/shared/hciemu.h"
+
+
+struct test_data {
+ struct mgmt *mgmt;
+ uint16_t mgmt_index;
+ struct hciemu *hciemu;
+ enum hciemu_type hciemu_type;
+};
+
+static void read_info_callback(uint8_t status, uint16_t length,
+ const void *param, void *user_data)
+{
+ struct test_data *data = tester_get_data();
+ const struct mgmt_rp_read_info *rp = param;
+ char addr[18];
+ uint16_t manufacturer;
+ uint32_t supported_settings, current_settings;
+
+ tester_print("Read Info callback");
+ tester_print(" Status: 0x%02x", status);
+
+ if (status || !param) {
+ tester_pre_setup_failed();
+ return;
+ }
+
+ ba2str(&rp->bdaddr, addr);
+ manufacturer = btohs(rp->manufacturer);
+ supported_settings = btohl(rp->supported_settings);
+ current_settings = btohl(rp->current_settings);
+
+ tester_print(" Address: %s", addr);
+ tester_print(" Version: 0x%02x", rp->version);
+ tester_print(" Manufacturer: 0x%04x", manufacturer);
+ tester_print(" Supported settings: 0x%08x", supported_settings);
+ tester_print(" Current settings: 0x%08x", current_settings);
+ tester_print(" Class: 0x%02x%02x%02x",
+ rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]);
+ tester_print(" Name: %s", rp->name);
+ tester_print(" Short name: %s", rp->short_name);
+
+ if (strcmp(hciemu_get_address(data->hciemu), addr)) {
+ tester_pre_setup_failed();
+ return;
+ }
+
+ tester_pre_setup_complete();
+}
+
+static void index_added_callback(uint16_t index, uint16_t length,
+ const void *param, void *user_data)
+{
+ struct test_data *data = tester_get_data();
+
+ tester_print("Index Added callback");
+ tester_print(" Index: 0x%04x", index);
+
+ data->mgmt_index = index;
+
+ mgmt_send(data->mgmt, MGMT_OP_READ_INFO, data->mgmt_index, 0, NULL,
+ read_info_callback, NULL, NULL);
+}
+
+static void index_removed_callback(uint16_t index, uint16_t length,
+ const void *param, void *user_data)
+{
+ struct test_data *data = tester_get_data();
+
+ tester_print("Index Removed callback");
+ tester_print(" Index: 0x%04x", index);
+
+ if (index != data->mgmt_index)
+ return;
+
+ mgmt_unregister_index(data->mgmt, data->mgmt_index);
+
+ mgmt_unref(data->mgmt);
+ data->mgmt = NULL;
+
+ tester_post_teardown_complete();
+}
+
+static void read_index_list_callback(uint8_t status, uint16_t length,
+ const void *param, void *user_data)
+{
+ struct test_data *data = tester_get_data();
+
+ tester_print("Read Index List callback");
+ tester_print(" Status: 0x%02x", status);
+
+ if (status || !param) {
+ tester_pre_setup_failed();
+ return;
+ }
+
+ mgmt_register(data->mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE,
+ index_added_callback, NULL, NULL);
+
+ mgmt_register(data->mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE,
+ index_removed_callback, NULL, NULL);
+
+ data->hciemu = hciemu_new(data->hciemu_type);
+ if (!data->hciemu) {
+ tester_warn("Failed to setup HCI emulation");
+ tester_pre_setup_failed();
+ return;
+ }
+
+ tester_print("New hciemu instance created");
+}
+
+static void test_pre_setup(const void *data)
+{
+ struct test_data *test_data = tester_get_data();
+
+ if (!tester_use_debug())
+ fclose(stderr);
+
+ test_data->mgmt = mgmt_new_default();
+ if (!test_data->mgmt) {
+ tester_warn("Failed to setup management interface");
+ tester_pre_setup_failed();
+ return;
+ }
+
+ mgmt_send(test_data->mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0,
+ NULL, read_index_list_callback, NULL, NULL);
+}
+
+static void test_post_teardown(const void *data)
+{
+ struct test_data *test_data = tester_get_data();
+
+ hciemu_unref(test_data->hciemu);
+ test_data->hciemu = NULL;
+}
+
+static void setup(const void *data)
+{
+ tester_setup_failed();
+}
+
+static void teardown(const void *data)
+{
+ tester_teardown_complete();
+}
+
+static void ipc_send_tc(const void *data)
+{
+}
+
+#define test_bredrle(name, data, test_setup, test, test_teardown) \
+ do { \
+ struct test_data *user; \
+ user = g_malloc0(sizeof(struct test_data)); \
+ if (!user) \
+ break; \
+ user->hciemu_type = HCIEMU_TYPE_BREDRLE; \
+ tester_add_full(name, data, test_pre_setup, test_setup, \
+ test, test_teardown, test_post_teardown, \
+ 3, user, g_free); \
+ } while (0)
+
+int main(int argc, char *argv[])
+{
+ tester_init(&argc, &argv);
+
+ test_bredrle("Test Dummy", NULL, setup, ipc_send_tc, teardown);
+
+ return tester_run();
+}
--
1.8.5.2
^ permalink raw reply related
* [PATCHv4 0/8] IPC negative tester
From: Jakub Tyszkowski @ 2014-01-16 8:38 UTC (permalink / raw)
To: linux-bluetooth
Following patchset adds IPC negative tester framework with few test cases
checking IPC's behaviour on daemon side. Expected daemon's behaviour is to
shut down gracefully in case of receiving invalid IPC data.
v2 changes:
* fixed few indentation issues
* fixed missing __attribute__((packed))
* fixed amount of data written for 'malformed data' test case
* fixed opcode for 'invalid service' test case
* added patch(8) with more 'malformed data' cases
v3 changes:
* changed license to GPL
* changed 'ipc-negative-tester' name to 'ipc-tester'
v4 changes:
* fixed typo in first test case and last commit's message
* fixed daemon shutdown handler function
Jakub Tyszkowski (8):
android/ipc-tester: Skeleton for ipc negative tester
android/ipc-tester: Run daemon in separate process
android/ipc-tester: Add IPC initialization
android/ipc-tester: Add daemon shutdown handler
android/ipc-tester: Add sending test data with ipc
android/ipc-tester: Register services
android/ipc-tester: Add basic test cases for IPC's daemon site
android/ipc-tester: Add more cases for malformed data
.gitignore | 1 +
android/Makefile.am | 17 ++
android/ipc-tester.c | 713 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 731 insertions(+)
create mode 100644 android/ipc-tester.c
--
1.8.5.2
^ permalink raw reply
* Re: [PATCH v2 1/5] Bluetooth: Track Secure Connections support of remote devices
From: Johan Hedberg @ 2014-01-16 8:38 UTC (permalink / raw)
To: Marcel Holtmann; +Cc: linux-bluetooth
In-Reply-To: <1389854262-62467-1-git-send-email-marcel@holtmann.org>
Hi Marcel,
On Wed, Jan 15, 2014, Marcel Holtmann wrote:
> It is important to know if Secure Connections support has been enabled
> for a given remote device. The information is provided in the remote
> host features page. So track this information and provide a simple
> helper function to extract the status.
>
> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
> ---
> include/net/bluetooth/hci_core.h | 8 ++++++++
> net/bluetooth/hci_event.c | 3 +++
> 2 files changed, 11 insertions(+)
All patches in this set have been applied to bluetooth-next. Thanks.
Johan
^ permalink raw reply
* [PATCH v2] Bluetooth: Queue incoming ACL data until BT_CONNECTED state is reached
From: johan.hedberg @ 2014-01-16 8:33 UTC (permalink / raw)
To: linux-bluetooth
From: Johan Hedberg <johan.hedberg@intel.com>
This patch adds a queue for incoming L2CAP data that's received before
l2cap_connect_cfm is called and processes the data once
l2cap_connect_cfm is called. This way we ensure that we have e.g. all
remote features before processing L2CAP signaling data (which is very
important for making the correct security decisions).
The processing of the pending rx data needs to be done through
queue_work since unlike l2cap_recv_acldata, l2cap_connect_cfm is called
with the hci_dev lock held which could cause potential deadlocks.
Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
---
v2: Updated to avoid call to skb_queue_empty()
include/net/bluetooth/l2cap.h | 3 +++
net/bluetooth/l2cap_core.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index dbc4a89984ca..40e15cd948c1 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -623,6 +623,9 @@ struct l2cap_conn {
__u32 rx_len;
__u8 tx_ident;
+ struct sk_buff_head pending_rx;
+ struct work_struct pending_rx_work;
+
__u8 disc_reason;
struct delayed_work security_timer;
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index b0ad2c752d73..001195968f58 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -63,6 +63,8 @@ static void l2cap_send_disconn_req(struct l2cap_chan *chan, int err);
static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
struct sk_buff_head *skbs, u8 event);
+static void process_pending_rx(struct work_struct *work);
+
static inline __u8 bdaddr_type(struct hci_conn *hcon, __u8 type)
{
if (hcon->type == LE_LINK) {
@@ -1546,6 +1548,8 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
}
mutex_unlock(&conn->chan_lock);
+
+ queue_work(hcon->hdev->workqueue, &conn->pending_rx_work);
}
/* Notify sockets that we cannot guaranty reliability anymore */
@@ -1671,6 +1675,9 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
kfree_skb(conn->rx_skb);
+ skb_queue_purge(&conn->pending_rx);
+ flush_work(&conn->pending_rx_work);
+
l2cap_unregister_all_users(conn);
mutex_lock(&conn->chan_lock);
@@ -1773,6 +1780,9 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon)
else
INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout);
+ skb_queue_head_init(&conn->pending_rx);
+ INIT_WORK(&conn->pending_rx_work, process_pending_rx);
+
conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
return conn;
@@ -7084,9 +7094,16 @@ drop:
static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
{
struct l2cap_hdr *lh = (void *) skb->data;
+ struct hci_conn *hcon = conn->hcon;
u16 cid, len;
__le16 psm;
+ if (hcon->state != BT_CONNECTED) {
+ BT_DBG("queueing pending rx skb");
+ skb_queue_tail(&conn->pending_rx, skb);
+ return;
+ }
+
skb_pull(skb, L2CAP_HDR_SIZE);
cid = __le16_to_cpu(lh->cid);
len = __le16_to_cpu(lh->len);
@@ -7132,6 +7149,24 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
}
}
+static void process_pending_rx(struct work_struct *work)
+{
+ struct l2cap_conn *conn = container_of(work, struct l2cap_conn,
+ pending_rx_work);
+
+ BT_DBG("");
+
+ while (1) {
+ struct sk_buff *skb;
+
+ skb = skb_dequeue(&conn->pending_rx);
+ if (!skb)
+ break;
+
+ l2cap_recv_frame(conn, skb);
+ }
+}
+
/* ---- L2CAP interface with lower layer (HCI) ---- */
int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
--
1.8.4.2
^ permalink raw reply related
* Re: [PATCH 1/5] android: Add android ipc tester
From: Szymon Janc @ 2014-01-16 8:20 UTC (permalink / raw)
To: Marcin Kraglak; +Cc: linux-bluetooth
In-Reply-To: <1389813150-16914-1-git-send-email-marcin.kraglak@tieto.com>
Hi Marcin,
On Wednesday 15 of January 2014 20:12:26 Marcin Kraglak wrote:
> This tool will test ipc library. First test case will check
> ipc_init() call.
> ---
> .gitignore | 1 +
> android/Makefile.am | 8 ++
> android/test-android-ipc.c | 277 +++++++++++++++++++++++++++++++++++++++++++++
I would name this android/test-ipc.c.
> 3 files changed, 286 insertions(+)
> create mode 100644 android/test-android-ipc.c
>
> diff --git a/.gitignore b/.gitignore
> index ac76fe2..0d0834b 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -114,3 +114,4 @@ android/bluetoothd
> android/haltest
> android/android-tester
> android/bluetoothd-snoop
> +android/test-android-ipc
> diff --git a/android/Makefile.am b/android/Makefile.am
> index 7806f79..271a3e8 100644
> --- a/android/Makefile.am
> +++ b/android/Makefile.am
> @@ -117,6 +117,14 @@ android_android_tester_LDADD = lib/libbluetooth-internal.la @GLIB_LIBS@
>
> android_android_tester_LDFLAGS = -pthread -ldl
>
> +noinst_PROGRAMS += android/test-android-ipc
> +
> +android_test_android_ipc_SOURCES = android/test-android-ipc.c \
> + src/shared/util.h src/shared/util.c \
> + src/log.h src/log.c \
> + android/ipc.c android/ipc.h
> +android_test_android_ipc_LDADD = @GLIB_LIBS@
> +
> plugin_LTLIBRARIES += android/audio.a2dp.default.la
>
> android_audio_a2dp_default_la_SOURCES = android/audio-msg.h \
> diff --git a/android/test-android-ipc.c b/android/test-android-ipc.c
> new file mode 100644
> index 0000000..6ac1175
> --- /dev/null
> +++ b/android/test-android-ipc.c
> @@ -0,0 +1,277 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2014 Intel Corporation. All rights reserved.
> + *
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +#include <stdio.h>
> +#include <errno.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +#include <inttypes.h>
> +#include <string.h>
> +#include <fcntl.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <sys/signalfd.h>
> +
> +#include <glib.h>
> +#include "src/shared/util.h"
> +#include "src/log.h"
> +#include "android/hal-msg.h"
> +#include "android/ipc.h"
> +
> +struct test_data {
> + uint32_t expected_signal;
> +};
> +
> +struct context {
> + GMainLoop *main_loop;
> +
> + int sk;
> +
> + guint source;
> + guint cmd_source;
> + guint notif_source;
> +
> + GIOChannel *cmd_io;
> + GIOChannel *notif_io;
> + GIOChannel *signal_io;
> +
> + guint signal_source;
> +
> + const struct test_data *data;
> +};
> +
> +static void context_quit(struct context *context)
> +{
> + g_main_loop_quit(context->main_loop);
> +}
> +
> +static gboolean cmd_watch(GIOChannel *io, GIOCondition cond,
> + gpointer user_data)
> +{
> + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
> + g_assert(FALSE);
> + return FALSE;
> + }
> +
> + return TRUE;
> +}
> +
> +static gboolean notif_watch(GIOChannel *io, GIOCondition cond,
> + gpointer user_data)
> +{
> + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
> + g_assert(FALSE);
> + return FALSE;
> + }
> +
> + return TRUE;
> +}
> +
> +static gboolean connect_handler(GIOChannel *io, GIOCondition cond,
> + gpointer user_data)
> +{
> + struct context *context = user_data;
> + GIOChannel *new_io;
> + GIOCondition watch_cond;
> + int sk;
> +
> + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
> + g_assert(FALSE);
> + return FALSE;
> + }
> +
> + g_assert(!context->cmd_source || !context->notif_source);
> +
> + sk = accept(context->sk, NULL, NULL);
> + g_assert(sk >= 0);
> +
> + new_io = g_io_channel_unix_new(sk);
> +
> + watch_cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
> +
> + if (context->cmd_source && !context->notif_source) {
> + context->notif_source = g_io_add_watch(new_io, watch_cond,
> + notif_watch, context);
> + g_assert(context->notif_source > 0);
> + context->notif_io = new_io;
> + }
> +
> + if (!context->cmd_source) {
> + context->cmd_source = g_io_add_watch(new_io, watch_cond,
> + cmd_watch, context);
> + context->cmd_io = new_io;
> + }
> +
> + if (context->cmd_source && context->notif_source)
> + context_quit(context);
> +
> + return TRUE;
> +}
> +
> +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond,
> + gpointer user_data)
> +{
> + struct context *context = user_data;
> + const struct test_data *test_data = context->data;
> + struct signalfd_siginfo si;
> + ssize_t result;
> + int fd;
> +
> + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP))
> + return FALSE;
> +
> + fd = g_io_channel_unix_get_fd(channel);
> +
> + result = read(fd, &si, sizeof(si));
> + if (result != sizeof(si))
> + return FALSE;
> +
> + g_assert(test_data->expected_signal == si.ssi_signo);
> + context_quit(context);
> + return TRUE;
> +}
> +
> +static guint setup_signalfd(gpointer user_data)
> +{
> + GIOChannel *channel;
> + guint source;
> + sigset_t mask;
> + int ret;
> + int fd;
> +
> + sigemptyset(&mask);
> + sigaddset(&mask, SIGINT);
> + sigaddset(&mask, SIGTERM);
> +
> + ret = sigprocmask(SIG_BLOCK, &mask, NULL);
> + g_assert(ret == 0);
> +
> + fd = signalfd(-1, &mask, 0);
> + g_assert(fd >= 0);
> +
> + channel = g_io_channel_unix_new(fd);
> +
> + g_io_channel_set_close_on_unref(channel, TRUE);
> + g_io_channel_set_encoding(channel, NULL, NULL);
> + g_io_channel_set_buffered(channel, FALSE);
> +
> + source = g_io_add_watch(channel,
> + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
> + signal_handler, user_data);
> +
> + g_io_channel_unref(channel);
> +
> + return source;
> +}
> +
> +static struct context *create_context(gconstpointer data)
> +{
> + struct context *context = g_new0(struct context, 1);
> + struct sockaddr_un addr;
> + GIOChannel *io;
> + int ret, sk;
> +
> + context->main_loop = g_main_loop_new(NULL, FALSE);
> + g_assert(context->main_loop);
> +
> + context->signal_source = setup_signalfd(context);
> + g_assert(context->signal_source);
> +
> + sk = socket(AF_LOCAL, SOCK_SEQPACKET, 0);
> + g_assert(sk >= 0);
> +
> + memset(&addr, 0, sizeof(addr));
> + addr.sun_family = AF_UNIX;
> +
> + memcpy(addr.sun_path, BLUEZ_HAL_SK_PATH, sizeof(BLUEZ_HAL_SK_PATH));
> +
> + ret = bind(sk, (struct sockaddr *) &addr, sizeof(addr));
> + g_assert(ret == 0);
> +
> + ret = listen(sk, 5);
> + g_assert(ret == 0);
> +
> + io = g_io_channel_unix_new(sk);
> +
> + g_io_channel_set_close_on_unref(io, TRUE);
> +
> + context->source = g_io_add_watch(io,
> + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
> + connect_handler, context);
> + g_assert(context->source > 0);
> +
> + g_io_channel_unref(io);
> +
> + context->sk = sk;
> + context->data = data;
> +
> + return context;
> +}
> +
> +static void execute_context(struct context *context)
> +{
> + g_main_loop_run(context->main_loop);
> +
> + g_io_channel_shutdown(context->notif_io, true, NULL);
> + g_io_channel_shutdown(context->cmd_io, true, NULL);
> + g_io_channel_unref(context->cmd_io);
> + g_io_channel_unref(context->notif_io);
> +
> + g_source_remove(context->notif_source);
> + g_source_remove(context->signal_source);
> + g_source_remove(context->cmd_source);
> + g_source_remove(context->source);
> +
> + g_main_loop_unref(context->main_loop);
> +
> + g_free(context);
> +}
> +
> +static void test_init(gconstpointer data)
> +{
> + struct context *context = create_context(data);
> +
> + ipc_init();
> +
> + execute_context(context);
> +
> + ipc_cleanup();
> +}
> +
> +static const struct test_data test_init_1 = {};
> +
> +int main(int argc, char *argv[])
> +{
> + g_test_init(&argc, &argv, NULL);
> +
> + if (g_test_verbose())
> + __btd_log_init("*", 0);
> +
> + g_test_add_data_func("/android_ipc/init", &test_init_1, test_init);
> +
> + return g_test_run();
> +}
>
--
Best regards,
Szymon Janc
^ permalink raw reply
* Re: [PATCH] android/pics: Add PICS and PIXIT for AVCTP
From: Szymon Janc @ 2014-01-16 8:06 UTC (permalink / raw)
To: Jakub Tyszkowski; +Cc: linux-bluetooth
In-Reply-To: <1389778295-846-1-git-send-email-jakub.tyszkowski@tieto.com>
Hi Jakub,
On Wednesday 15 of January 2014 10:31:35 Jakub Tyszkowski wrote:
> Add PICS/PIXIT for AVCTP, targeting Android 4.4.
> ---
> android/Makefile.am | 1 +
> android/pics-avctp.txt | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
> android/pixit-avctp.txt | 39 +++++++++++++++++++++++++
> 3 files changed, 115 insertions(+)
> create mode 100644 android/pics-avctp.txt
> create mode 100644 android/pixit-avctp.txt
>
> diff --git a/android/Makefile.am b/android/Makefile.am
> index 7806f79..cd4a526 100644
> --- a/android/Makefile.am
> +++ b/android/Makefile.am
> @@ -147,4 +147,5 @@ EXTRA_DIST += android/Android.mk android/hal-ipc-api.txt android/README \
> android/pics-l2cap.txt android/pixit-l2cap.txt \
> android/pics-avrcp.txt android/pixit-avrcp.txt \
> android/pics-a2dp.txt android/pixit-a2dp.txt \
> + android/pics-avctp.txt android/pixit-avctp.txt \
> android/pts-l2cap.txt
> diff --git a/android/pics-avctp.txt b/android/pics-avctp.txt
> new file mode 100644
> index 0000000..939ffdb
> --- /dev/null
> +++ b/android/pics-avctp.txt
> @@ -0,0 +1,75 @@
> +AVCTP PICS for the PTS tool.
> +
> +PTS version: 5.0
> +
> +* - different than PTS defaults
> +# - not yet implemented/supported
> +
> +M - mandatory if such role selected
> +O - optional
> +
> + Protocol Version
> +-------------------------------------------------------------------------------
> +Parameter Name Selected Description
> +-------------------------------------------------------------------------------
> +TSPC_AVCTP_0_1 False AVCTP 1.0 (C.1)
> +TSPC_AVCTP_0_2 True (*) AVCTP 1.2 (C.1)
> +TSPC_AVCTP_0_3 False AVCTP 1.3 (C.1)
> +TSPC_AVCTP_0_4 False AVCTP 1.4 (C.1)
> +-------------------------------------------------------------------------------
> +C.1: Mandatory to support only one Protocol Version.
> +-------------------------------------------------------------------------------
> +
> +
> + Roles
> +-------------------------------------------------------------------------------
> +Parameter Name Selected Description
> +-------------------------------------------------------------------------------
> +TSPC_AVCTP_1_1 False Controller (C.1)
> +TSPC_AVCTP_1_2 True (*) Target (C.1)
> +-------------------------------------------------------------------------------
> +C.1: Mandatory to support at least one of the defined roles.
> +-------------------------------------------------------------------------------
> +
> +
> + Controller Features
> +-------------------------------------------------------------------------------
> +Parameter Name Selected Description
> +-------------------------------------------------------------------------------
> +TSPC_AVCTP_2_1 False Message fragmentation (O)
> +TSPC_AVCTP_2_2 False (*) Transaction label management (M)
> +TSPC_AVCTP_2_3 False (*) Packet type field management (M)
> +TSPC_AVCTP_2_4 False (*) Message type field management (M)
> +TSPC_AVCTP_2_5 False (*) PID field management (M)
> +TSPC_AVCTP_2_6 False (*) IPID field mangement (M)
> +TSPC_AVCTP_2_7 False (*) Message information management (M)
> +TSPC_AVCTP_2_8 False Event registration for message reception (O)
> +TSPC_AVCTP_2_9 False Event registration for connection request (O)
> +TSPC_AVCTP_2_10 False Event registration for disconnection (O)
> +TSPC_AVCTP_2_11 False Connect request (O)
> +TSPC_AVCTP_2_12 False Disconnect request (O)
> +TSPC_AVCTP_2_13 False Send message (O)
> +TSPC_AVCTP_2_14 False Support for multiple AVCTP channel establishment
> + (O)
> +-------------------------------------------------------------------------------
> +
> +
> + Target Features
> +-------------------------------------------------------------------------------
> +Parameter Name Selected Description
> +-------------------------------------------------------------------------------
> +TSPC_AVCTP_3_1 True (*) Message fragmentation (O)
> +TSPC_AVCTP_3_2 True Transaction label management (M)
> +TSPC_AVCTP_3_3 True Packet type field management (M)
> +TSPC_AVCTP_3_4 True Message type field management (M)
> +TSPC_AVCTP_3_5 True PID field management (M)
> +TSPC_AVCTP_3_6 True IPID field management (M)
> +TSPC_AVCTP_3_7 True Message information management (M)
> +TSPC_AVCTP_3_8 True (*) Event registration for message reception (O)
> +TSPC_AVCTP_3_9 True (*) Event registration for connection request (O)
> +TSPC_AVCTP_3_10 True (*) Event registration for disconnection request (O)
> +TSPC_AVCTP_3_11 True (*) Connect request (O)
> +TSPC_AVCTP_3_12 True (*) Disconnect request (O)
> +TSPC_AVCTP_3_13 True (*) Send message (O)
> +TSPC_AVCTP_ALL False Enables all test cases when set to TRUE
> +-------------------------------------------------------------------------------
> diff --git a/android/pixit-avctp.txt b/android/pixit-avctp.txt
> new file mode 100644
> index 0000000..c5782fd
> --- /dev/null
> +++ b/android/pixit-avctp.txt
> @@ -0,0 +1,39 @@
> +AVCTP PIXIT for the PTS tool.
> +
> +PTS version: 5.0
> +
> +* - different than PTS defaults
> +& - should be set to IUT Bluetooth address
> +# - should be set to PTS's bin/audio folder
> +
> + Required PIXIT settings
> +-------------------------------------------------------------------------------
> +Parameter Name Value
> +-------------------------------------------------------------------------------
> +TSPX_avctp_psm 0017
> +TSPX_avctp_profile_id 110E
> +TSPX_connect_avdtp TRUE
> +TSPX_avctp_tester_command_data
> +TSPX_avctp_tester_response_data
> +TSPX_avctp_iut_command_data
> +TSPX_avctp_iut_response_data
> +TSPX_bd_addr_iut 08606E414394 (&)
> +TSPX_pin_code 0000
> +TSPX_delete_link_key FALSE
> +TSPX_security_enabled FALSE
> +TSPX_class_of_device 20050C
> +TSPX_player_feature_bitmask 0000000000000007FFF00070000000000
> +TSPX_avrcp_version
> +TSPX_establish_avdtp_stream TRUE
> +TSPX_tester_av_role
> +TSPX_time_guard 300000
> +TSPX_avrcp_only FALSE
> +TSPX_use_implicit_send TRUE
> +TSPX_media_directory C:\Program Files\Bluetooth SIG\Bluetooth PTS\
> + bin\audio (#)
> +TSPX_no_confirmations FALSE
> +TSPX_auth_password 0000
> +TSPX_auth_user_id PTS
> +TSPX_rfcomm_channel 8
> +TSPX_l2cap_psm 1011
> +-------------------------------------------------------------------------------
>
Pushed, thanks.
--
Best regards,
Szymon Janc
^ permalink raw reply
* Re: [PATCH 1/4] android: Add simple rotation of snoop file
From: Szymon Janc @ 2014-01-16 8:02 UTC (permalink / raw)
To: Andrzej Kaczmarek; +Cc: linux-bluetooth
In-Reply-To: <1389819699-12055-1-git-send-email-andrzej.kaczmarek@tieto.com>
Hi Andrzej,
On Wednesday 15 of January 2014 22:01:36 Andrzej Kaczmarek wrote:
> Already existing snoop file is renamed by adding ".old" suffix before
> new one is created. This is useful in case phone is restarted so logs
> are not overwritten and for this reason it's only applied in case
> default snoop file name is used.
> ---
> android/bluetoothd-snoop.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/android/bluetoothd-snoop.c b/android/bluetoothd-snoop.c
> index 02f44e9..23e0bfd 100644
> --- a/android/bluetoothd-snoop.c
> +++ b/android/bluetoothd-snoop.c
> @@ -206,6 +206,9 @@ int main(int argc, char *argv[])
>
> mainloop_set_signal(&mask, signal_callback, NULL, NULL);
>
> + if (!strcmp(DEFAULT_SNOOP_FILE, path))
> + rename(DEFAULT_SNOOP_FILE, DEFAULT_SNOOP_FILE ".old");
> +
> if (open_monitor(path) < 0) {
> printf("Failed to start bluetoothd_snoop\n");
> return EXIT_FAILURE;
>
All patches in this set applied, thanks.
(after changing commits order to not break build in 1/4)
--
Best regards,
Szymon Janc
^ permalink raw reply
* Re: Wireshark and new BlueZ BTSNOOP format
From: Michal Labedzki @ 2014-01-16 7:00 UTC (permalink / raw)
To: linux-bluetooth@vger.kernel.org development; +Cc: Szymon Janc, Marcel Holtmann
In-Reply-To: <66980755-B25D-4EE6-A443-5A60AD75351D@holtmann.org>
Hello,
I want to announce that Wireshark from SVN revision 54699 support new
"Bluetooth monitor" BTSNOOP file format. This means that you are able
to open file created by btmon in Wireshark. Support for capturing by
libpcap is already requested to libpcap/tcpdump team (formally,
implementation is done)
By the way: there is also support for Ubertooth for Low Energy Link
Layer, but via USB (usbmon) interface (and ubertooth host tool, for
example: "ubertooth btle -f").
Pozdrawiam / Best regards
---------------------------------------------------------------------------=
----------------------------------
Micha=C5=82 =C5=81ab=C4=99dzki, Software Engineer
Tieto Corporation
Product Development Services
http://www.tieto.com / http://www.tieto.pl
---
ASCII: Michal Labedzki
location: Swobodna 1 Street, 50-088 Wroc=C5=82aw, Poland
room: 5.01 (desk next to 5.08)
---
Please note: The information contained in this message may be legally
privileged and confidential and protected from disclosure. If the
reader of this message is not the intended recipient, you are hereby
notified that any unauthorised use, distribution or copying of this
communication is strictly prohibited. If you have received this
communication in error, please notify us immediately by replying to
the message and deleting it from your computer. Thank You.
---
Please consider the environment before printing this e-mail.
---
Tieto Poland sp=C3=B3=C5=82ka z ograniczon=C4=85 odpowiedzialno=C5=9Bci=C4=
=85 z siedzib=C4=85 w
Szczecinie, ul. Malczewskiego 26. Zarejestrowana w S=C4=85dzie Rejonowym
Szczecin-Centrum w Szczecinie, XIII Wydzia=C5=82 Gospodarczy Krajowego
Rejestru S=C4=85dowego pod numerem 0000124858. NIP: 8542085557. REGON:
812023656. Kapita=C5=82 zak=C5=82adowy: 4 271500 PLN
^ permalink raw reply
* [PATCH v2 5/5] Bluetooth: Add debugfs entry to show Secure Connections Only mode
From: Marcel Holtmann @ 2014-01-16 6:37 UTC (permalink / raw)
To: linux-bluetooth
For debugging purposes of Secure Connection Only support a simple
debugfs entry is used to indicate if this mode is active or not.
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
---
include/net/bluetooth/hci.h | 1 +
net/bluetooth/hci_core.c | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+)
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 0064a9aa5df1..232c07804ca8 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -124,6 +124,7 @@ enum {
HCI_LE_SCAN,
HCI_SSP_ENABLED,
HCI_SC_ENABLED,
+ HCI_SC_ONLY,
HCI_HS_ENABLED,
HCI_LE_ENABLED,
HCI_ADVERTISING,
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 499ec1b1095d..369d30750417 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -461,6 +461,24 @@ static const struct file_operations force_sc_support_fops = {
.llseek = default_llseek,
};
+static ssize_t sc_only_mode_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct hci_dev *hdev = file->private_data;
+ char buf[3];
+
+ buf[0] = test_bit(HCI_SC_ONLY, &hdev->dev_flags) ? 'Y': 'N';
+ buf[1] = '\n';
+ buf[2] = '\0';
+ return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
+}
+
+static const struct file_operations sc_only_mode_fops = {
+ .open = simple_open,
+ .read = sc_only_mode_read,
+ .llseek = default_llseek,
+};
+
static int idle_timeout_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
@@ -1491,6 +1509,8 @@ static int __hci_init(struct hci_dev *hdev)
hdev, &ssp_debug_mode_fops);
debugfs_create_file("force_sc_support", 0644, hdev->debugfs,
hdev, &force_sc_support_fops);
+ debugfs_create_file("sc_only_mode", 0444, hdev->debugfs,
+ hdev, &sc_only_mode_fops);
}
if (lmp_sniff_capable(hdev)) {
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 4/5] Bluetooth: Handle security level 4 for RFCOMM connections
From: Marcel Holtmann @ 2014-01-16 6:37 UTC (permalink / raw)
To: linux-bluetooth
With the introduction of security level 4, the RFCOMM sockets need to
be made aware of this new level. This change ensures that the pairing
requirements are set correctly for these connections.
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
---
include/net/bluetooth/rfcomm.h | 1 +
net/bluetooth/rfcomm/core.c | 4 +++-
net/bluetooth/rfcomm/sock.c | 12 +++++++++++-
3 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h
index 486213a1aed8..c312cfc4e922 100644
--- a/include/net/bluetooth/rfcomm.h
+++ b/include/net/bluetooth/rfcomm.h
@@ -295,6 +295,7 @@ struct rfcomm_conninfo {
#define RFCOMM_LM_TRUSTED 0x0008
#define RFCOMM_LM_RELIABLE 0x0010
#define RFCOMM_LM_SECURE 0x0020
+#define RFCOMM_LM_FIPS 0x0040
#define rfcomm_pi(sk) ((struct rfcomm_pinfo *) sk)
diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
index facd8a79c038..ba115d472f7b 100644
--- a/net/bluetooth/rfcomm/core.c
+++ b/net/bluetooth/rfcomm/core.c
@@ -216,6 +216,7 @@ static int rfcomm_check_security(struct rfcomm_dlc *d)
switch (d->sec_level) {
case BT_SECURITY_HIGH:
+ case BT_SECURITY_FIPS:
auth_type = HCI_AT_GENERAL_BONDING_MITM;
break;
case BT_SECURITY_MEDIUM:
@@ -2085,7 +2086,8 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt)
set_bit(RFCOMM_SEC_PENDING, &d->flags);
rfcomm_dlc_set_timer(d, RFCOMM_AUTH_TIMEOUT);
continue;
- } else if (d->sec_level == BT_SECURITY_HIGH) {
+ } else if (d->sec_level == BT_SECURITY_HIGH ||
+ d->sec_level == BT_SECURITY_FIPS) {
set_bit(RFCOMM_ENC_DROP, &d->flags);
continue;
}
diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c
index 3c2d3e4aa2f5..fb8158af1f39 100644
--- a/net/bluetooth/rfcomm/sock.c
+++ b/net/bluetooth/rfcomm/sock.c
@@ -648,6 +648,11 @@ static int rfcomm_sock_setsockopt_old(struct socket *sock, int optname, char __u
break;
}
+ if (opt & RFCOMM_LM_FIPS) {
+ err = -EINVAL;
+ break;
+ }
+
if (opt & RFCOMM_LM_AUTH)
rfcomm_pi(sk)->sec_level = BT_SECURITY_LOW;
if (opt & RFCOMM_LM_ENCRYPT)
@@ -762,7 +767,11 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, char __u
break;
case BT_SECURITY_HIGH:
opt = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT |
- RFCOMM_LM_SECURE;
+ RFCOMM_LM_SECURE;
+ break;
+ case BT_SECURITY_FIPS:
+ opt = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT |
+ RFCOMM_LM_SECURE | RFCOMM_LM_FIPS;
break;
default:
opt = 0;
@@ -774,6 +783,7 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, char __u
if (put_user(opt, (u32 __user *) optval))
err = -EFAULT;
+
break;
case RFCOMM_CONNINFO:
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 3/5] Bluetooth: Handle security level 4 for L2CAP connections
From: Marcel Holtmann @ 2014-01-16 6:37 UTC (permalink / raw)
To: linux-bluetooth
With the introduction of security level 4, the L2CAP sockets need to
be made aware of this new level. This change ensures that the pairing
requirements are set correctly for these connections.
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
---
include/net/bluetooth/l2cap.h | 1 +
net/bluetooth/l2cap_core.c | 11 ++++++++---
net/bluetooth/l2cap_sock.c | 10 ++++++++++
3 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index dbc4a89984ca..c695083eee2b 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -91,6 +91,7 @@ struct l2cap_conninfo {
#define L2CAP_LM_TRUSTED 0x0008
#define L2CAP_LM_RELIABLE 0x0010
#define L2CAP_LM_SECURE 0x0020
+#define L2CAP_LM_FIPS 0x0040
/* L2CAP command codes */
#define L2CAP_COMMAND_REJ 0x01
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index b0ad2c752d73..3f0dd552cb2b 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -737,6 +737,7 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
case L2CAP_CHAN_RAW:
switch (chan->sec_level) {
case BT_SECURITY_HIGH:
+ case BT_SECURITY_FIPS:
return HCI_AT_DEDICATED_BONDING_MITM;
case BT_SECURITY_MEDIUM:
return HCI_AT_DEDICATED_BONDING;
@@ -749,7 +750,8 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
if (chan->sec_level == BT_SECURITY_LOW)
chan->sec_level = BT_SECURITY_SDP;
}
- if (chan->sec_level == BT_SECURITY_HIGH)
+ if (chan->sec_level == BT_SECURITY_HIGH ||
+ chan->sec_level == BT_SECURITY_FIPS)
return HCI_AT_NO_BONDING_MITM;
else
return HCI_AT_NO_BONDING;
@@ -759,7 +761,8 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
if (chan->sec_level == BT_SECURITY_LOW)
chan->sec_level = BT_SECURITY_SDP;
- if (chan->sec_level == BT_SECURITY_HIGH)
+ if (chan->sec_level == BT_SECURITY_HIGH ||
+ chan->sec_level == BT_SECURITY_FIPS)
return HCI_AT_NO_BONDING_MITM;
else
return HCI_AT_NO_BONDING;
@@ -768,6 +771,7 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
default:
switch (chan->sec_level) {
case BT_SECURITY_HIGH:
+ case BT_SECURITY_FIPS:
return HCI_AT_GENERAL_BONDING_MITM;
case BT_SECURITY_MEDIUM:
return HCI_AT_GENERAL_BONDING;
@@ -7206,7 +7210,8 @@ static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt)
if (encrypt == 0x00) {
if (chan->sec_level == BT_SECURITY_MEDIUM) {
__set_chan_timer(chan, L2CAP_ENC_TIMEOUT);
- } else if (chan->sec_level == BT_SECURITY_HIGH)
+ } else if (chan->sec_level == BT_SECURITY_HIGH ||
+ chan->sec_level == BT_SECURITY_FIPS)
l2cap_chan_close(chan, ECONNREFUSED);
} else {
if (chan->sec_level == BT_SECURITY_MEDIUM)
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 20ef748b2906..4aa6704f0b33 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -432,6 +432,10 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
opt = L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT |
L2CAP_LM_SECURE;
break;
+ case BT_SECURITY_FIPS:
+ opt = L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT |
+ L2CAP_LM_SECURE | L2CAP_LM_FIPS;
+ break;
default:
opt = 0;
break;
@@ -445,6 +449,7 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname,
if (put_user(opt, (u32 __user *) optval))
err = -EFAULT;
+
break;
case L2CAP_CONNINFO:
@@ -699,6 +704,11 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname,
break;
}
+ if (opt & L2CAP_LM_FIPS) {
+ err = -EINVAL;
+ break;
+ }
+
if (opt & L2CAP_LM_AUTH)
chan->sec_level = BT_SECURITY_LOW;
if (opt & L2CAP_LM_ENCRYPT)
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 2/5] Bluetooth: Introduce requirements for security level 4
From: Marcel Holtmann @ 2014-01-16 6:37 UTC (permalink / raw)
To: linux-bluetooth
The security level 4 is a new strong security requirement that is based
around 128-bit equivalent strength for link and encryption keys required
using FIPS approved algorithms. Which means that E0, SAFER+ and P-192
are not allowed. Only connections created with P-256 resulting from
using Secure Connections support are allowed.
This security level needs to be enforced when Secure Connection Only
mode is enabled for a controller or a service requires FIPS compliant
strong security. Currently it is not possible to enable either of
these two cases. This patch just puts in the foundation for being
able to handle security level 4 in the future.
It should be noted that devices or services with security level 4
requirement can only communicate using Bluetooth 4.1 controllers
with support for Secure Connections. There is no backward compatibilty
if used with older hardware.
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
---
include/net/bluetooth/bluetooth.h | 1 +
include/net/bluetooth/hci.h | 1 +
net/bluetooth/hci_conn.c | 18 +++++++++++++-----
3 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index f4f9ee466791..904777c1cd24 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -65,6 +65,7 @@ struct bt_security {
#define BT_SECURITY_LOW 1
#define BT_SECURITY_MEDIUM 2
#define BT_SECURITY_HIGH 3
+#define BT_SECURITY_FIPS 4
#define BT_DEFER_SETUP 7
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 2bc19881e250..0064a9aa5df1 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -313,6 +313,7 @@ enum {
#define HCI_LM_TRUSTED 0x0008
#define HCI_LM_RELIABLE 0x0010
#define HCI_LM_SECURE 0x0020
+#define HCI_LM_FIPS 0x0040
/* Authentication types */
#define HCI_AT_NO_BONDING 0x00
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index cf96b3438a91..0266bd8e4913 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -800,10 +800,17 @@ int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type)
if (!(conn->link_mode & HCI_LM_AUTH))
goto auth;
- /* An authenticated combination key has sufficient security for any
- security level. */
- if (conn->key_type == HCI_LK_AUTH_COMBINATION_P192 ||
- conn->key_type == HCI_LK_AUTH_COMBINATION_P256)
+ /* An authenticated FIPS approved combination key has sufficient
+ * security for security level 4. */
+ if (conn->key_type == HCI_LK_AUTH_COMBINATION_P256 &&
+ sec_level == BT_SECURITY_FIPS)
+ goto encrypt;
+
+ /* An authenticated combination key has sufficient security for
+ security level 3. */
+ if ((conn->key_type == HCI_LK_AUTH_COMBINATION_P192 ||
+ conn->key_type == HCI_LK_AUTH_COMBINATION_P256) &&
+ sec_level == BT_SECURITY_HIGH)
goto encrypt;
/* An unauthenticated combination key has sufficient security for
@@ -818,7 +825,8 @@ int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type)
is generated using maximum PIN code length (16).
For pre 2.1 units. */
if (conn->key_type == HCI_LK_COMBINATION &&
- (sec_level != BT_SECURITY_HIGH || conn->pin_length == 16))
+ (sec_level == BT_SECURITY_MEDIUM || sec_level == BT_SECURITY_LOW ||
+ conn->pin_length == 16))
goto encrypt;
auth:
--
1.8.4.2
^ permalink raw reply related
* [PATCH v2 1/5] Bluetooth: Track Secure Connections support of remote devices
From: Marcel Holtmann @ 2014-01-16 6:37 UTC (permalink / raw)
To: linux-bluetooth
It is important to know if Secure Connections support has been enabled
for a given remote device. The information is provided in the remote
host features page. So track this information and provide a simple
helper function to extract the status.
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
---
include/net/bluetooth/hci_core.h | 8 ++++++++
net/bluetooth/hci_event.c | 3 +++
2 files changed, 11 insertions(+)
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 66e96ebffe97..8d225e4ea2ce 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -448,6 +448,7 @@ enum {
HCI_CONN_LE_SMP_PEND,
HCI_CONN_MGMT_CONNECTED,
HCI_CONN_SSP_ENABLED,
+ HCI_CONN_SC_ENABLED,
HCI_CONN_POWER_SAVE,
HCI_CONN_REMOTE_OOB,
HCI_CONN_6LOWPAN,
@@ -460,6 +461,13 @@ static inline bool hci_conn_ssp_enabled(struct hci_conn *conn)
test_bit(HCI_CONN_SSP_ENABLED, &conn->flags);
}
+static inline bool hci_conn_sc_enabled(struct hci_conn *conn)
+{
+ struct hci_dev *hdev = conn->hdev;
+ return test_bit(HCI_SC_ENABLED, &hdev->dev_flags) &&
+ test_bit(HCI_CONN_SC_ENABLED, &conn->flags);
+}
+
static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c)
{
struct hci_conn_hash *h = &hdev->conn_hash;
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index da1eca1c43db..8c44bbe19add 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -2898,6 +2898,9 @@ static void hci_remote_ext_features_evt(struct hci_dev *hdev,
* features do not indicate SSP support */
clear_bit(HCI_CONN_SSP_ENABLED, &conn->flags);
}
+
+ if (ev->features[0] & LMP_HOST_SC)
+ set_bit(HCI_CONN_SC_ENABLED, &conn->flags);
}
if (conn->state != BT_CONFIG)
--
1.8.4.2
^ permalink raw reply related
* Re: [PATCH] Bluetooth: Avoid use of session socket after the session gets freed
From: Marcel Holtmann @ 2014-01-16 6:23 UTC (permalink / raw)
To: dean_jenkins
Cc: linux-bluetooth@vger.kernel.org development, Gustavo F. Padovan,
vitaly_kuzmichev
In-Reply-To: <1389532808-1845-1-git-send-email-Dean_Jenkins@mentor.com>
Hi Dean,
> The commits 08c30aca9e698faddebd34f81e1196295f9dc063 "Bluetooth: Remove
> RFCOMM session refcnt" and 8ff52f7d04d9cc31f1e81dcf9a2ba6335ed34905
> "Bluetooth: Return RFCOMM session ptrs to avoid freed session"
> allow rfcomm_recv_ua and rfcomm_session_close to delete the session
> (and free the corresponding socket) and propagate NULL session pointer
> to the upper callers.
>
> Additional fix is required to terminate the loop in rfcomm_process_rx
> function to avoid use of freed 'sk' memory.
>
> The issue is only reproducible with kernel option CONFIG_PAGE_POISONING
> enabled making freed memory being changed and filled up with fixed char
> value used to unmask use-after-free issues.
>
> Signed-off-by: Vitaly Kuzmichev <Vitaly_Kuzmichev@mentor.com>
> Acked-by: Dean Jenkins <Dean_Jenkins@mentor.com>
> ---
> net/bluetooth/rfcomm/core.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
> index facd8a7..5632146 100644
> --- a/net/bluetooth/rfcomm/core.c
> +++ b/net/bluetooth/rfcomm/core.c
> @@ -1857,7 +1857,7 @@ static struct rfcomm_session *rfcomm_process_rx(struct rfcomm_session *s)
> BT_DBG("session %p state %ld qlen %d", s, s->state, skb_queue_len(&sk->sk_receive_queue));
>
> /* Get data directly from socket receive queue without copying it. */
> - while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
> + while (s && (skb = skb_dequeue(&sk->sk_receive_queue))) {
> skb_orphan(skb);
> if (!skb_linearize(skb))
> s = rfcomm_recv_frame(s, skb);
Let do a break here.
if (!s)
break;
Regards
Marcel
^ permalink raw reply
* Re: [PATCH] Bluetooth: BCSP fails to ACK re-transmitted frames from the peer
From: Marcel Holtmann @ 2014-01-16 6:20 UTC (permalink / raw)
To: dean_jenkins
Cc: linux-bluetooth@vger.kernel.org development, Gustavo F. Padovan
In-Reply-To: <1389544033-16974-1-git-send-email-Dean_Jenkins@mentor.com>
Hi Dean,
> Send an ACK frame with the current txack value in response to
> every received reliable frame unless a TX reliable frame is being
> sent. This modification allows re-transmitted frames from the remote
> peer to be acknowledged rather than ignored. It means that the remote
> peer knows which frame number to start re-transmitting from.
>
> Without this modification, the recovery time to a missing frame
> from the remote peer was unnecessarily being extended because the
> headers of the out of order reliable frames were being discarded rather
> than being processed. The frame headers of received frames will
> indicate whether the local peer's transmissions have been
> acknowledged by the remote peer. Therefore, the local peer may
> unnecessarily re-transmit despite the remote peer already indicating
> that the frame had been acknowledged in out of order reliable frame.
>
> Signed-off-by: Dean Jenkins <djenkins@mentor.com>
> ---
> drivers/bluetooth/hci_bcsp.c | 94 ++++++++++++++++++++++++++++----------------
> 1 file changed, 60 insertions(+), 34 deletions(-)
>
> diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c
> index 0bc87f7..449de29 100644
> --- a/drivers/bluetooth/hci_bcsp.c
> +++ b/drivers/bluetooth/hci_bcsp.c
> @@ -473,13 +473,30 @@ static inline void bcsp_unslip_one_byte(struct bcsp_struct *bcsp, unsigned char
> static void bcsp_complete_rx_pkt(struct hci_uart *hu)
> {
> struct bcsp_struct *bcsp = hu->priv;
> - int pass_up;
> + int pass_up = 0;
>
> if (bcsp->rx_skb->data[0] & 0x80) { /* reliable pkt */
> BT_DBG("Received seqno %u from card", bcsp->rxseq_txack);
> - bcsp->rxseq_txack++;
> - bcsp->rxseq_txack %= 0x8;
> - bcsp->txack_req = 1;
> +
> + /* check the rx sequence number is as expected */
> + if ((bcsp->rx_skb->data[0] & 0x07) == bcsp->rxseq_txack) {
> + bcsp->rxseq_txack++;
> + bcsp->rxseq_txack %= 0x8;
> + } else {
> + /*
> + * handle re-transmitted packet or
> + * when packet was missed
> + */
> + BT_ERR("Out-of-order packet arrived, got %u expected %u",
> + bcsp->rx_skb->data[0] & 0x07,
> + bcsp->rxseq_txack);
please make sure to use the coding style of the network subsystem.
> +
> + /* do not process out-of-order packet payload */
> + pass_up = 2;
> + }
> +
> + /* send current txack value to all recieved reliable packets */
> + bcsp->txack_req = 1;
>
> /* If needed, transmit an ack pkt */
> hci_uart_tx_wakeup(hu);
> @@ -488,26 +505,35 @@ static void bcsp_complete_rx_pkt(struct hci_uart *hu)
> bcsp->rxack = (bcsp->rx_skb->data[0] >> 3) & 0x07;
> BT_DBG("Request for pkt %u from card", bcsp->rxack);
>
> + /*
> + * handle recieved ACK indications,
> + * including those from out-of-order packets
> + */
> bcsp_pkt_cull(bcsp);
> - if ((bcsp->rx_skb->data[1] & 0x0f) == 6 &&
> - bcsp->rx_skb->data[0] & 0x80) {
> - bt_cb(bcsp->rx_skb)->pkt_type = HCI_ACLDATA_PKT;
> - pass_up = 1;
> - } else if ((bcsp->rx_skb->data[1] & 0x0f) == 5 &&
> - bcsp->rx_skb->data[0] & 0x80) {
> - bt_cb(bcsp->rx_skb)->pkt_type = HCI_EVENT_PKT;
> - pass_up = 1;
> - } else if ((bcsp->rx_skb->data[1] & 0x0f) == 7) {
> - bt_cb(bcsp->rx_skb)->pkt_type = HCI_SCODATA_PKT;
> - pass_up = 1;
> - } else if ((bcsp->rx_skb->data[1] & 0x0f) == 1 &&
> - !(bcsp->rx_skb->data[0] & 0x80)) {
> - bcsp_handle_le_pkt(hu);
> - pass_up = 0;
> - } else
> - pass_up = 0;
> -
> - if (!pass_up) {
> +
> + if (pass_up != 2) {
> + if ((bcsp->rx_skb->data[1] & 0x0f) == 6 &&
> + bcsp->rx_skb->data[0] & 0x80) {
Same here. Please use network subsystem indentation.
> + bt_cb(bcsp->rx_skb)->pkt_type = HCI_ACLDATA_PKT;
> + pass_up = 1;
> + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 5 &&
> + bcsp->rx_skb->data[0] & 0x80) {
> + bt_cb(bcsp->rx_skb)->pkt_type = HCI_EVENT_PKT;
> + pass_up = 1;
> + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 7) {
> + bt_cb(bcsp->rx_skb)->pkt_type = HCI_SCODATA_PKT;
> + pass_up = 1;
> + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 1 &&
> + !(bcsp->rx_skb->data[0] & 0x80)) {
> + bcsp_handle_le_pkt(hu);
> + pass_up = 0;
> + } else
> + pass_up = 0;
Network subsystem requires { } if any branch uses it.
> + }
> +
> + switch (pass_up) {
> + case 0:
> + {
> struct hci_event_hdr hdr;
> u8 desc = (bcsp->rx_skb->data[1] & 0x0f);
>
> @@ -532,11 +558,21 @@ static void bcsp_complete_rx_pkt(struct hci_uart *hu)
> }
> } else
> kfree_skb(bcsp->rx_skb);
> - } else {
> + break;
> + }
> + case 1:
> /* Pull out BCSP hdr */
> skb_pull(bcsp->rx_skb, 4);
>
> hci_recv_frame(hu->hdev, bcsp->rx_skb);
> + break;
> + default:
> + /*
> + * ignore packet payload of already ACKed re-transmitted
> + * packets or when a packet was missed in the BCSP window
> + */
> + kfree_skb(bcsp->rx_skb);
> + break;
> }
>
> bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
> @@ -582,16 +618,6 @@ static int bcsp_recv(struct hci_uart *hu, void *data, int count)
> bcsp->rx_count = 0;
> continue;
> }
> - if (bcsp->rx_skb->data[0] & 0x80 /* reliable pkt */
> - && (bcsp->rx_skb->data[0] & 0x07) != bcsp->rxseq_txack) {
> - BT_ERR ("Out-of-order packet arrived, got %u expected %u",
> - bcsp->rx_skb->data[0] & 0x07, bcsp->rxseq_txack);
> -
> - kfree_skb(bcsp->rx_skb);
> - bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
> - bcsp->rx_count = 0;
> - continue;
> - }
> bcsp->rx_state = BCSP_W4_DATA;
> bcsp->rx_count = (bcsp->rx_skb->data[1] >> 4) +
> (bcsp->rx_skb->data[2] << 4); /* May be 0 */
Regards
Marcel
^ permalink raw reply
* Re: [PATCH] Bluetooth: Queue incoming ACL data until BT_CONNECTED state is reached
From: Marcel Holtmann @ 2014-01-16 6:17 UTC (permalink / raw)
To: Johan Hedberg; +Cc: linux-bluetooth@vger.kernel.org development
In-Reply-To: <1389778231-20307-1-git-send-email-johan.hedberg@gmail.com>
Hi Johan,
> This patch adds a queue for incoming L2CAP data that's received before
> l2cap_connect_cfm is called and processes the data once
> l2cap_connect_cfm is called. This way we ensure that we have e.g. all
> remote features before processing L2CAP signaling data (which is very
> important for making the correct security decisions).
>
> The processing of the pending rx data needs to be done through
> queue_work since unlike l2cap_recv_acldata, l2cap_connect_cfm is called
> with the hci_dev lock held which could cause potential deadlocks.
>
> Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
> ---
> This patch is e.g. needed for the "L2CAP BR/EDR Server - Security Block"
> l2cap-tester test case to pass reliably.
>
> include/net/bluetooth/l2cap.h | 3 +++
> net/bluetooth/l2cap_core.c | 33 +++++++++++++++++++++++++++++++++
> 2 files changed, 36 insertions(+)
>
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index dbc4a89984ca..40e15cd948c1 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -623,6 +623,9 @@ struct l2cap_conn {
> __u32 rx_len;
> __u8 tx_ident;
>
> + struct sk_buff_head pending_rx;
> + struct work_struct pending_rx_work;
> +
> __u8 disc_reason;
>
> struct delayed_work security_timer;
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index b0ad2c752d73..f2ee479a87a7 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -63,6 +63,8 @@ static void l2cap_send_disconn_req(struct l2cap_chan *chan, int err);
> static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
> struct sk_buff_head *skbs, u8 event);
>
> +static void process_pending_rx(struct work_struct *work);
> +
do we really need this forward declaration?
> static inline __u8 bdaddr_type(struct hci_conn *hcon, __u8 type)
> {
> if (hcon->type == LE_LINK) {
> @@ -1546,6 +1548,8 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
> }
>
> mutex_unlock(&conn->chan_lock);
> +
> + queue_work(hcon->hdev->workqueue, &conn->pending_rx_work);
> }
>
> /* Notify sockets that we cannot guaranty reliability anymore */
> @@ -1671,6 +1675,9 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
>
> kfree_skb(conn->rx_skb);
>
> + skb_queue_purge(&conn->pending_rx);
> + flush_work(&conn->pending_rx_work);
> +
> l2cap_unregister_all_users(conn);
>
> mutex_lock(&conn->chan_lock);
> @@ -1773,6 +1780,9 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon)
> else
> INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout);
>
> + skb_queue_head_init(&conn->pending_rx);
> + INIT_WORK(&conn->pending_rx_work, process_pending_rx);
> +
> conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
>
> return conn;
> @@ -7084,9 +7094,16 @@ drop:
> static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
> {
> struct l2cap_hdr *lh = (void *) skb->data;
> + struct hci_conn *hcon = conn->hcon;
> u16 cid, len;
> __le16 psm;
>
> + if (hcon->state != BT_CONNECTED) {
> + BT_DBG("queueing pending rx skb");
> + skb_queue_tail(&conn->pending_rx, skb);
> + return;
> + }
> +
> skb_pull(skb, L2CAP_HDR_SIZE);
> cid = __le16_to_cpu(lh->cid);
> len = __le16_to_cpu(lh->len);
> @@ -7132,6 +7149,22 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
> }
> }
>
> +static void process_pending_rx(struct work_struct *work)
> +{
> + struct l2cap_conn *conn = container_of(work, struct l2cap_conn,
> + pending_rx_work);
> +
> + BT_DBG("");
> +
> + while (!skb_queue_empty(&conn->pending_rx)) {
> + struct sk_buff *skb;
> +
> + skb = skb_dequeue(&conn->pending_rx);
> +
> + l2cap_recv_frame(conn, skb);
> + }
> +}
> +
I realize we have done this skb_queue_empty check and then skb_dequeue a bit, but that seems a little bit pointless.
skb_dequeue is taking a spinlock, but even then checking for empty first and then dequeuing it seems more complicated that needed here. We could just dequeue it check if skb == NULL.
while (1) {
struct sk_buff *skb;
skb = sbk_dequeue(&conn->pending_rx);
if (!skb)
break;
l2cap_recv_frame(conn, skb);
}
And we have similar things in l2cap_streaming_send, l2cap_chan_send and l2cap_le_credits. I get the feeling we should change all of these.
Regards
Marcel
^ permalink raw reply
* Re: [PATCH] Bluetooth: Fix mgmt error code for negative PIN response
From: Marcel Holtmann @ 2014-01-16 6:01 UTC (permalink / raw)
To: Johan Hedberg; +Cc: linux-bluetooth@vger.kernel.org development
In-Reply-To: <1389626153-25740-1-git-send-email-johan.hedberg@gmail.com>
Hi Johan,
> The NOT_PAIRED status is only really suitable for operations where being
> paired is a pre-requisite. Using it e.g. for the mgmt_pair_device
> command seems unintuitive. In the case that either the local or the
> remote user responds with a negative PIN Code response the "PIN or Key
> Missing" HCI status will be generated. This patch changes the mapping of
> this status from the NOT_PAIRED mgmt status to the more intuitive
> AUTH_FAILED mgmt status.
>
> Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
> ---
> I had trouble deciding whether we should use REJECTED or AUTH_FAILED
> here, but eventually went with AUTH_FAILED. If someone thinks REJECTED
> is better and has convincing reasons for it I can change it.
>
> net/bluetooth/mgmt.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
patch has been applied to bluetooth-next tree.
Regards
Marcel
^ permalink raw reply
* Re: [PATCH v6] Bluetooth: Add hci_h4p driver
From: Marcel Holtmann @ 2014-01-16 3:01 UTC (permalink / raw)
To: Pavel Machek
Cc: Pali Rohár,
Ивайло Димитров,
Gustavo F. Padovan, Johan Hedberg, linux-kernel,
linux-bluetooth@vger.kernel.org development, Ville Tervo,
Sebastian Reichel
In-Reply-To: <20140111002801.GA20216@amd.pavel.ucw.cz>
Hi Pavel,
> Add hci_h4p bluetooth driver to bluetooth-next. This device is used
> for example on Nokia N900 cell phone.
>
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> Thanks-to: Sebastian Reichel <sre@debian.org>
> Thanks-to: Joe Perches <joe@perches.com>
>
> ---
>
> Changes from v5: Comment fixes and some refactoring suggested by
> Joe Perches. Please apply.
>
> diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
> index 11a6104..a53e8c7 100644
> --- a/drivers/bluetooth/Kconfig
> +++ b/drivers/bluetooth/Kconfig
> @@ -241,5 +241,15 @@ config BT_WILINK
> core driver to communicate with the BT core of the combo chip.
>
> Say Y here to compile support for Texas Instrument's WiLink7 driver
> - into the kernel or say M to compile it as module.
> + into the kernel or say M to compile it as module (btwilink).
> +
> +config BT_NOKIA_H4P
> + tristate "HCI driver with H4 Nokia extensions"
> + depends on BT && ARCH_OMAP
> + help
> + Bluetooth HCI driver with H4 extensions. This driver provides
> + support for H4+ Bluetooth chip with vendor-specific H4 extensions.
> +
> + Say Y here to compile support for h4 extended devices into the kernel
> + or say M to compile it as module (btnokia_h4p).
> endmenu
> diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
> index 9fe8a87..6f25db2 100644
> --- a/drivers/bluetooth/Makefile
> +++ b/drivers/bluetooth/Makefile
> @@ -31,4 +31,8 @@ hci_uart-$(CONFIG_BT_HCIUART_ATH3K) += hci_ath.o
> hci_uart-$(CONFIG_BT_HCIUART_3WIRE) += hci_h5.o
> hci_uart-objs := $(hci_uart-y)
>
> +obj-$(CONFIG_BT_NOKIA_H4P) += btnokia_h4p.o
> +btnokia_h4p-objs := nokia_core.o nokia_fw.o nokia_uart.o nokia_fw-csr.o \
> + nokia_fw-bcm.o nokia_fw-ti1273.o
> +
> ccflags-y += -D__CHECK_ENDIAN__
> diff --git a/drivers/bluetooth/hci_h4p.h b/drivers/bluetooth/hci_h4p.h
> new file mode 100644
> index 0000000..fd7a640
> --- /dev/null
> +++ b/drivers/bluetooth/hci_h4p.h
can we please get the naming straight. File names do not start with hci_ anymore. We moved away from it since that term is too generic.
> @@ -0,0 +1,228 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#ifndef __DRIVERS_BLUETOOTH_HCI_H4P_H
> +#define __DRIVERS_BLUETOOTH_HCI_H4P_H
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +#include <net/bluetooth/hci.h>
> +
> +#define FW_NAME_TI1271_PRELE "ti1273_prele.bin"
> +#define FW_NAME_TI1271_LE "ti1273_le.bin"
> +#define FW_NAME_TI1271 "ti1273.bin"
> +#define FW_NAME_BCM2048 "bcmfw.bin"
> +#define FW_NAME_CSR "bc4fw.bin”
We do these have to be global in a header file. This should be confined to the specific firmware part.
> +
> +#define UART_SYSC_OMAP_RESET 0x03
> +#define UART_SYSS_RESETDONE 0x01
> +#define UART_OMAP_SCR_EMPTY_THR 0x08
> +#define UART_OMAP_SCR_WAKEUP 0x10
> +#define UART_OMAP_SSR_WAKEUP 0x02
> +#define UART_OMAP_SSR_TXFULL 0x01
> +
> +#define UART_OMAP_SYSC_IDLEMODE 0x03
> +#define UART_OMAP_SYSC_IDLEMASK (3 << UART_OMAP_SYSC_IDLEMODE)
> +
> +#define UART_OMAP_SYSC_FORCE_IDLE (0 << UART_OMAP_SYSC_IDLEMODE)
> +#define UART_OMAP_SYSC_NO_IDLE (1 << UART_OMAP_SYSC_IDLEMODE)
> +#define UART_OMAP_SYSC_SMART_IDLE (2 << UART_OMAP_SYSC_IDLEMODE)
> +
> +#define H4P_TRANSFER_MODE 1
> +#define H4P_SCHED_TRANSFER_MODE 2
> +#define H4P_ACTIVE_MODE 3
> +
> +struct hci_h4p_info {
Can we please get rid of the hci_ prefix for everything. Copying from drivers that are over 10 years old is not a good idea. Please look at recent ones.
> + struct timer_list lazy_release;
Timer? Not delayed work?
> + struct hci_dev *hdev;
> + spinlock_t lock;
> +
> + void __iomem *uart_base;
> + unsigned long uart_phys_base;
> + int irq;
> + struct device *dev;
> + u8 chip_type;
> + u8 bt_wakeup_gpio;
> + u8 host_wakeup_gpio;
> + u8 reset_gpio;
> + u8 reset_gpio_shared;
> + u8 bt_sysclk;
> + u8 man_id;
> + u8 ver_id;
> +
> + struct sk_buff_head fw_queue;
> + struct sk_buff *alive_cmd_skb;
> + struct completion init_completion;
> + struct completion fw_completion;
> + struct completion test_completion;
> + int fw_error;
> + int init_error;
> +
> + struct sk_buff_head txq;
> +
> + struct sk_buff *rx_skb;
> + long rx_count;
> + unsigned long rx_state;
> + unsigned long garbage_bytes;
> +
> + u8 bd_addr[6];
> + struct sk_buff_head *fw_q;
> +
> + int pm_enabled;
> + int tx_enabled;
> + int autorts;
> + int rx_enabled;
> + unsigned long pm_flags;
> +
> + int tx_clocks_en;
> + int rx_clocks_en;
> + spinlock_t clocks_lock;
> + struct clk *uart_iclk;
> + struct clk *uart_fclk;
> + atomic_t clk_users;
> + u16 dll;
> + u16 dlh;
> + u16 ier;
> + u16 mdr1;
> + u16 efr;
> +};
> +
> +struct hci_h4p_radio_hdr {
> + __u8 evt;
> + __u8 dlen;
> +} __attribute__ ((packed));
> +
> +struct hci_h4p_neg_hdr {
> + __u8 dlen;
> +} __attribute__ ((packed));
> +#define H4P_NEG_HDR_SIZE 1
> +
> +#define H4P_NEG_REQ 0x00
> +#define H4P_NEG_ACK 0x20
> +#define H4P_NEG_NAK 0x40
> +
> +#define H4P_PROTO_PKT 0x44
> +#define H4P_PROTO_BYTE 0x4c
> +
> +#define H4P_ID_CSR 0x02
> +#define H4P_ID_BCM2048 0x04
> +#define H4P_ID_TI1271 0x31
> +
> +struct hci_h4p_neg_cmd {
> + __u8 ack;
> + __u16 baud;
> + __u16 unused1;
> + __u8 proto;
> + __u16 sys_clk;
> + __u16 unused2;
> +} __attribute__ ((packed));
> +
> +struct hci_h4p_neg_evt {
> + __u8 ack;
> + __u16 baud;
> + __u16 unused1;
> + __u8 proto;
> + __u16 sys_clk;
> + __u16 unused2;
> + __u8 man_id;
> + __u8 ver_id;
> +} __attribute__ ((packed));
> +
> +#define H4P_ALIVE_REQ 0x55
> +#define H4P_ALIVE_RESP 0xcc
> +
> +struct hci_h4p_alive_hdr {
> + __u8 dlen;
> +} __attribute__ ((packed));
> +#define H4P_ALIVE_HDR_SIZE 1
> +
> +struct hci_h4p_alive_pkt {
> + __u8 mid;
> + __u8 unused;
> +} __attribute__ ((packed));
> +
> +#define MAX_BAUD_RATE 921600
> +#define BC4_MAX_BAUD_RATE 3692300
> +#define UART_CLOCK 48000000
> +#define BT_INIT_DIVIDER 320
> +#define BT_BAUDRATE_DIVIDER 384000000
> +#define BT_SYSCLK_DIV 1000
> +#define INIT_SPEED 120000
> +
> +#define H4_TYPE_SIZE 1
> +#define H4_RADIO_HDR_SIZE 2
> +
> +/* H4+ packet types */
> +#define H4_CMD_PKT 0x01
> +#define H4_ACL_PKT 0x02
> +#define H4_SCO_PKT 0x03
> +#define H4_EVT_PKT 0x04
> +#define H4_NEG_PKT 0x06
> +#define H4_ALIVE_PKT 0x07
> +#define H4_RADIO_PKT 0x08
> +
> +/* TX states */
> +#define WAIT_FOR_PKT_TYPE 1
> +#define WAIT_FOR_HEADER 2
> +#define WAIT_FOR_DATA 3
> +
> +struct hci_fw_event {
> + struct hci_event_hdr hev;
> + struct hci_ev_cmd_complete cmd;
> + u8 status;
> +} __attribute__ ((packed));
> +
> +int hci_h4p_send_alive_packet(struct hci_h4p_info *info);
> +
> +void hci_h4p_bcm_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb);
> +int hci_h4p_bcm_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue);
> +
> +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb);
> +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue);
> +
> +void hci_h4p_ti1273_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb);
> +int hci_h4p_ti1273_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue);
> +
> +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue);
> +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue);
> +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb);
> +
> +void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val);
> +u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset);
> +void hci_h4p_set_rts(struct hci_h4p_info *info, int active);
> +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, int timeout_ms);
> +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which);
> +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which);
> +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed);
> +int hci_h4p_reset_uart(struct hci_h4p_info *info);
> +void hci_h4p_init_uart(struct hci_h4p_info *info);
> +void hci_h4p_enable_tx(struct hci_h4p_info *info);
> +void hci_h4p_store_regs(struct hci_h4p_info *info);
> +void hci_h4p_restore_regs(struct hci_h4p_info *info);
> +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable);
These are a lot of public functions. Are they all really needed or can the code be done smart.
> +
> +#endif /* __DRIVERS_BLUETOOTH_HCI_H4P_H */
> diff --git a/drivers/bluetooth/nokia_core.c b/drivers/bluetooth/nokia_core.c
> new file mode 100644
> index 0000000..54ec594
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_core.c
> @@ -0,0 +1,1205 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + * Thanks to all the Nokia people that helped with this driver,
> + * including Ville Tervo and Roger Quadros.
> + *
> + * Power saving functionality was removed from this driver to make
> + * merging easier.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/errno.h>
> +#include <linux/delay.h>
> +#include <linux/spinlock.h>
> +#include <linux/serial_reg.h>
> +#include <linux/skbuff.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/gpio.h>
> +#include <linux/timer.h>
> +#include <linux/kthread.h>
> +#include <linux/io.h>
> +#include <linux/completion.h>
> +#include <linux/sizes.h>
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +#include <net/bluetooth/hci.h>
> +
> +#include <linux/platform_data/hci-h4p.h>
> +
> +#include "hci_h4p.h"
> +
> +/* This should be used in function that cannot release clocks */
> +static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&info->clocks_lock, flags);
> + if (enable && !*clock) {
> + BT_DBG("Enabling %p", clock);
> + clk_prepare_enable(info->uart_fclk);
> + clk_prepare_enable(info->uart_iclk);
> + if (atomic_read(&info->clk_users) == 0)
> + hci_h4p_restore_regs(info);
> + atomic_inc(&info->clk_users);
> + }
> +
> + if (!enable && *clock) {
> + BT_DBG("Disabling %p", clock);
> + if (atomic_dec_and_test(&info->clk_users))
> + hci_h4p_store_regs(info);
> + clk_disable_unprepare(info->uart_fclk);
> + clk_disable_unprepare(info->uart_iclk);
> + }
> +
> + *clock = enable;
> + spin_unlock_irqrestore(&info->clocks_lock, flags);
> +}
> +
> +static void hci_h4p_lazy_clock_release(unsigned long data)
> +{
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&info->lock, flags);
> + if (!info->tx_enabled)
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +/* Power management functions */
> +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable)
> +{
> + u8 v;
> +
> + v = hci_h4p_inb(info, UART_OMAP_SYSC);
> + v &= ~(UART_OMAP_SYSC_IDLEMASK);
> +
> + if (enable)
> + v |= UART_OMAP_SYSC_SMART_IDLE;
> + else
> + v |= UART_OMAP_SYSC_NO_IDLE;
> +
> + hci_h4p_outb(info, UART_OMAP_SYSC, v);
> +}
> +
> +static inline void h4p_schedule_pm(struct hci_h4p_info *info)
> +{
> +}
> +
> +static void hci_h4p_disable_tx(struct hci_h4p_info *info)
> +{
> + if (!info->pm_enabled)
> + return;
> +
> + /* Re-enable smart-idle */
> + hci_h4p_smart_idle(info, 1);
> +
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + mod_timer(&info->lazy_release, jiffies + msecs_to_jiffies(100));
> + info->tx_enabled = 0;
> +}
> +
> +void hci_h4p_enable_tx(struct hci_h4p_info *info)
> +{
> + unsigned long flags;
> +
> + if (!info->pm_enabled)
> + return;
> +
> + h4p_schedule_pm(info);
> +
> + spin_lock_irqsave(&info->lock, flags);
> + del_timer(&info->lazy_release);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + info->tx_enabled = 1;
> + gpio_set_value(info->bt_wakeup_gpio, 1);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + /*
> + * Disable smart-idle as UART TX interrupts
> + * are not wake-up capable
> + */
> + hci_h4p_smart_idle(info, 0);
> +
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +static void hci_h4p_disable_rx(struct hci_h4p_info *info)
> +{
> + if (!info->pm_enabled)
> + return;
> +
> + info->rx_enabled = 0;
> +
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR)
> + return;
> +
> + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT))
> + return;
> +
> + __hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + info->autorts = 0;
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> +}
> +
> +static void hci_h4p_enable_rx(struct hci_h4p_info *info)
> +{
> + if (!info->pm_enabled)
> + return;
> +
> + h4p_schedule_pm(info);
> +
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 1);
> + info->rx_enabled = 1;
> +
> + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT))
> + return;
> +
> + __hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> + info->autorts = 1;
> +}
> +
> +/* Negotiation functions */
> +int hci_h4p_send_alive_packet(struct hci_h4p_info *info)
> +{
> + struct hci_h4p_alive_hdr *hdr;
> + struct hci_h4p_alive_pkt *pkt;
> + struct sk_buff *skb;
> + unsigned long flags;
> + int len;
> +
> + BT_DBG("Sending alive packet");
> +
> + len = H4_TYPE_SIZE + sizeof(*hdr) + sizeof(*pkt);
> + skb = bt_skb_alloc(len, GFP_KERNEL);
> + if (!skb)
> + return -ENOMEM;
> +
> + memset(skb->data, 0x00, len);
> + *skb_put(skb, 1) = H4_ALIVE_PKT;
> + hdr = (struct hci_h4p_alive_hdr *)skb_put(skb, sizeof(*hdr));
> + hdr->dlen = sizeof(*pkt);
> + pkt = (struct hci_h4p_alive_pkt *)skb_put(skb, sizeof(*pkt));
> + pkt->mid = H4P_ALIVE_REQ;
> +
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + BT_DBG("Alive packet sent");
> +
> + return 0;
> +}
> +
> +static void hci_h4p_alive_packet(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + struct hci_h4p_alive_hdr *hdr;
> + struct hci_h4p_alive_pkt *pkt;
> +
> + BT_DBG("Received alive packet");
> + hdr = (struct hci_h4p_alive_hdr *)skb->data;
> + if (hdr->dlen != sizeof(*pkt)) {
> + dev_err(info->dev, "Corrupted alive message\n");
> + info->init_error = -EIO;
> + goto finish_alive;
> + }
> +
> + pkt = (struct hci_h4p_alive_pkt *)skb_pull(skb, sizeof(*hdr));
> + if (pkt->mid != H4P_ALIVE_RESP) {
> + dev_err(info->dev, "Could not negotiate hci_h4p settings\n");
> + info->init_error = -EINVAL;
> + }
> +
> +finish_alive:
> + complete(&info->init_completion);
> + kfree_skb(skb);
> +}
> +
> +static int hci_h4p_send_negotiation(struct hci_h4p_info *info)
> +{
> + struct hci_h4p_neg_cmd *neg_cmd;
> + struct hci_h4p_neg_hdr *neg_hdr;
> + struct sk_buff *skb;
> + unsigned long flags;
> + int err, len;
> + u16 sysclk;
> +
> + BT_DBG("Sending negotiation..");
> +
> + switch (info->bt_sysclk) {
> + case 1:
> + sysclk = 12000;
> + break;
> + case 2:
> + sysclk = 38400;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + len = sizeof(*neg_cmd) + sizeof(*neg_hdr) + H4_TYPE_SIZE;
> + skb = bt_skb_alloc(len, GFP_KERNEL);
> + if (!skb)
> + return -ENOMEM;
> +
> + memset(skb->data, 0x00, len);
> + *skb_put(skb, 1) = H4_NEG_PKT;
> + neg_hdr = (struct hci_h4p_neg_hdr *)skb_put(skb, sizeof(*neg_hdr));
> + neg_cmd = (struct hci_h4p_neg_cmd *)skb_put(skb, sizeof(*neg_cmd));
> +
> + neg_hdr->dlen = sizeof(*neg_cmd);
> + neg_cmd->ack = H4P_NEG_REQ;
> + neg_cmd->baud = cpu_to_le16(BT_BAUDRATE_DIVIDER/MAX_BAUD_RATE);
> + neg_cmd->proto = H4P_PROTO_BYTE;
> + neg_cmd->sys_clk = cpu_to_le16(sysclk);
> +
> + hci_h4p_change_speed(info, INIT_SPEED);
> +
> + hci_h4p_set_rts(info, 1);
> + info->init_error = 0;
> + init_completion(&info->init_completion);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + if (!wait_for_completion_interruptible_timeout(&info->init_completion,
> + msecs_to_jiffies(1000)))
> + return -ETIMEDOUT;
> +
> + if (info->init_error < 0)
> + return info->init_error;
> +
> + /* Change to operational settings */
> + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + hci_h4p_set_rts(info, 0);
> + hci_h4p_change_speed(info, MAX_BAUD_RATE);
> +
> + err = hci_h4p_wait_for_cts(info, 1, 100);
> + if (err < 0)
> + return err;
> +
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> + init_completion(&info->init_completion);
> + err = hci_h4p_send_alive_packet(info);
> +
> + if (err < 0)
> + return err;
> +
> + if (!wait_for_completion_interruptible_timeout(&info->init_completion,
> + msecs_to_jiffies(1000)))
> + return -ETIMEDOUT;
> +
> + if (info->init_error < 0)
> + return info->init_error;
> +
> + BT_DBG("Negotiation successful");
> + return 0;
> +}
> +
> +static void hci_h4p_negotiation_packet(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + struct hci_h4p_neg_hdr *hdr;
> + struct hci_h4p_neg_evt *evt;
> +
> + hdr = (struct hci_h4p_neg_hdr *)skb->data;
> + if (hdr->dlen != sizeof(*evt)) {
> + info->init_error = -EIO;
> + goto finish_neg;
> + }
> +
> + evt = (struct hci_h4p_neg_evt *)skb_pull(skb, sizeof(*hdr));
> +
> + if (evt->ack != H4P_NEG_ACK) {
> + dev_err(info->dev, "Could not negotiate hci_h4p settings\n");
> + info->init_error = -EINVAL;
> + }
> +
> + info->man_id = evt->man_id;
> + info->ver_id = evt->ver_id;
> +
> +finish_neg:
> +
> + complete(&info->init_completion);
> + kfree_skb(skb);
> +}
> +
> +/* H4 packet handling functions */
> +static int hci_h4p_get_hdr_len(struct hci_h4p_info *info, u8 pkt_type)
> +{
> + long retval;
> +
> + switch (pkt_type) {
> + case H4_EVT_PKT:
> + retval = HCI_EVENT_HDR_SIZE;
> + break;
> + case H4_ACL_PKT:
> + retval = HCI_ACL_HDR_SIZE;
> + break;
> + case H4_SCO_PKT:
> + retval = HCI_SCO_HDR_SIZE;
> + break;
> + case H4_NEG_PKT:
> + retval = H4P_NEG_HDR_SIZE;
> + break;
> + case H4_ALIVE_PKT:
> + retval = H4P_ALIVE_HDR_SIZE;
> + break;
> + case H4_RADIO_PKT:
> + retval = H4_RADIO_HDR_SIZE;
> + break;
> + default:
> + dev_err(info->dev, "Unknown H4 packet type 0x%.2x\n", pkt_type);
> + retval = -1;
> + break;
> + }
> +
> + return retval;
> +}
> +
> +static unsigned int hci_h4p_get_data_len(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + long retval = -1;
> + struct hci_acl_hdr *acl_hdr;
> + struct hci_sco_hdr *sco_hdr;
> + struct hci_event_hdr *evt_hdr;
> + struct hci_h4p_neg_hdr *neg_hdr;
> + struct hci_h4p_alive_hdr *alive_hdr;
> + struct hci_h4p_radio_hdr *radio_hdr;
> +
> + switch (bt_cb(skb)->pkt_type) {
> + case H4_EVT_PKT:
> + evt_hdr = (struct hci_event_hdr *)skb->data;
> + retval = evt_hdr->plen;
> + break;
> + case H4_ACL_PKT:
> + acl_hdr = (struct hci_acl_hdr *)skb->data;
> + retval = le16_to_cpu(acl_hdr->dlen);
> + break;
> + case H4_SCO_PKT:
> + sco_hdr = (struct hci_sco_hdr *)skb->data;
> + retval = sco_hdr->dlen;
> + break;
> + case H4_RADIO_PKT:
> + radio_hdr = (struct hci_h4p_radio_hdr *)skb->data;
> + retval = radio_hdr->dlen;
> + break;
> + case H4_NEG_PKT:
> + neg_hdr = (struct hci_h4p_neg_hdr *)skb->data;
> + retval = neg_hdr->dlen;
> + break;
> + case H4_ALIVE_PKT:
> + alive_hdr = (struct hci_h4p_alive_hdr *)skb->data;
> + retval = alive_hdr->dlen;
> + break;
> + }
> +
> + return retval;
> +}
> +
> +static inline void hci_h4p_recv_frame(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + if (unlikely(!test_bit(HCI_RUNNING, &info->hdev->flags))) {
> + switch (bt_cb(skb)->pkt_type) {
> + case H4_NEG_PKT:
> + hci_h4p_negotiation_packet(info, skb);
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + return;
> + case H4_ALIVE_PKT:
> + hci_h4p_alive_packet(info, skb);
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + return;
> + }
> +
> + if (!test_bit(HCI_UP, &info->hdev->flags)) {
> + BT_DBG("fw_event");
> + hci_h4p_parse_fw_event(info, skb);
> + return;
> + }
> + }
> +
> + hci_recv_frame(info->hdev, skb);
> + BT_DBG("Frame sent to upper layer");
> +}
> +
> +static inline void hci_h4p_handle_byte(struct hci_h4p_info *info, u8 byte)
> +{
> + switch (info->rx_state) {
> + case WAIT_FOR_PKT_TYPE:
> + bt_cb(info->rx_skb)->pkt_type = byte;
> + info->rx_count = hci_h4p_get_hdr_len(info, byte);
> + if (info->rx_count < 0) {
> + info->hdev->stat.err_rx++;
> + kfree_skb(info->rx_skb);
> + info->rx_skb = NULL;
> + } else {
> + info->rx_state = WAIT_FOR_HEADER;
> + }
> + break;
> + case WAIT_FOR_HEADER:
> + info->rx_count--;
> + *skb_put(info->rx_skb, 1) = byte;
> + if (info->rx_count != 0)
> + break;
> + info->rx_count = hci_h4p_get_data_len(info, info->rx_skb);
> + if (info->rx_count > skb_tailroom(info->rx_skb)) {
> + dev_err(info->dev, "frame too long\n");
> + info->garbage_bytes = info->rx_count
> + - skb_tailroom(info->rx_skb);
> + kfree_skb(info->rx_skb);
> + info->rx_skb = NULL;
> + break;
> + }
> + info->rx_state = WAIT_FOR_DATA;
> + break;
> + case WAIT_FOR_DATA:
> + info->rx_count--;
> + *skb_put(info->rx_skb, 1) = byte;
> + break;
> + default:
> + WARN_ON(1);
> + break;
> + }
> +
> + if (info->rx_count == 0) {
> + /* H4+ devices should always send word aligned packets */
> + if (!(info->rx_skb->len % 2))
> + info->garbage_bytes++;
> + hci_h4p_recv_frame(info, info->rx_skb);
> + info->rx_skb = NULL;
> + }
> +}
> +
> +static void hci_h4p_rx_tasklet(unsigned long data)
> +{
> + u8 byte;
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> +
> + BT_DBG("tasklet woke up");
> + BT_DBG("rx_tasklet woke up");
> +
> + while (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR) {
> + byte = hci_h4p_inb(info, UART_RX);
> + if (info->garbage_bytes) {
> + info->garbage_bytes--;
> + continue;
> + }
> + if (info->rx_skb == NULL) {
> + info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE,
> + GFP_ATOMIC | GFP_DMA);
> + if (!info->rx_skb) {
> + dev_err(info->dev,
> + "No memory for new packet\n");
> + goto finish_rx;
> + }
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + info->rx_skb->dev = (void *)info->hdev;
> + }
> + info->hdev->stat.byte_rx++;
> + hci_h4p_handle_byte(info, byte);
> + }
> +
> + if (!info->rx_enabled) {
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT &&
> + info->autorts) {
> + __hci_h4p_set_auto_ctsrts(info, 0 , UART_EFR_RTS);
> + info->autorts = 0;
> + }
> + /* Flush posted write to avoid spurious interrupts */
> + hci_h4p_inb(info, UART_OMAP_SCR);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> + }
> +
> +finish_rx:
> + BT_DBG("rx_ended");
> +}
> +
> +static void hci_h4p_tx_tasklet(unsigned long data)
> +{
> + unsigned int sent = 0;
> + struct sk_buff *skb;
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> +
> + BT_DBG("tasklet woke up");
> + BT_DBG("tx_tasklet woke up");
> +
> + if (info->autorts != info->rx_enabled) {
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) {
> + if (info->autorts && !info->rx_enabled) {
> + __hci_h4p_set_auto_ctsrts(info, 0,
> + UART_EFR_RTS);
> + info->autorts = 0;
> + }
> + if (!info->autorts && info->rx_enabled) {
> + __hci_h4p_set_auto_ctsrts(info, 1,
> + UART_EFR_RTS);
> + info->autorts = 1;
> + }
> + } else {
> + hci_h4p_outb(info, UART_OMAP_SCR,
> + hci_h4p_inb(info, UART_OMAP_SCR) |
> + UART_OMAP_SCR_EMPTY_THR);
> + goto finish_tx;
> + }
> + }
> +
> + skb = skb_dequeue(&info->txq);
> + if (!skb) {
> + /* No data in buffer */
> + BT_DBG("skb ready");
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) {
> + hci_h4p_outb(info, UART_IER,
> + hci_h4p_inb(info, UART_IER) &
> + ~UART_IER_THRI);
> + hci_h4p_inb(info, UART_OMAP_SCR);
> + hci_h4p_disable_tx(info);
> + return;
> + }
> + hci_h4p_outb(info, UART_OMAP_SCR,
> + hci_h4p_inb(info, UART_OMAP_SCR) |
> + UART_OMAP_SCR_EMPTY_THR);
> + goto finish_tx;
> + }
> +
> + /* Copy data to tx fifo */
> + while (!(hci_h4p_inb(info, UART_OMAP_SSR) & UART_OMAP_SSR_TXFULL) &&
> + (sent < skb->len)) {
> + hci_h4p_outb(info, UART_TX, skb->data[sent]);
> + sent++;
> + }
> +
> + info->hdev->stat.byte_tx += sent;
> + if (skb->len == sent) {
> + kfree_skb(skb);
> + } else {
> + skb_pull(skb, sent);
> + skb_queue_head(&info->txq, skb);
> + }
> +
> + hci_h4p_outb(info, UART_OMAP_SCR, hci_h4p_inb(info, UART_OMAP_SCR) &
> + ~UART_OMAP_SCR_EMPTY_THR);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> +
> +finish_tx:
> + /* Flush posted write to avoid spurious interrupts */
> + hci_h4p_inb(info, UART_OMAP_SCR);
> +
> +}
> +
> +static irqreturn_t hci_h4p_interrupt(int irq, void *data)
> +{
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> + u8 iir, msr;
> + int ret;
> +
> + ret = IRQ_NONE;
> +
> + iir = hci_h4p_inb(info, UART_IIR);
> + if (iir & UART_IIR_NO_INT)
> + return IRQ_HANDLED;
> +
> + BT_DBG("In interrupt handler iir 0x%.2x", iir);
> +
> + iir &= UART_IIR_ID;
> +
> + if (iir == UART_IIR_MSI) {
> + msr = hci_h4p_inb(info, UART_MSR);
> + ret = IRQ_HANDLED;
> + }
> + if (iir == UART_IIR_RLSI) {
> + hci_h4p_inb(info, UART_RX);
> + hci_h4p_inb(info, UART_LSR);
> + ret = IRQ_HANDLED;
> + }
> +
> + if (iir == UART_IIR_RDI) {
> + hci_h4p_rx_tasklet((unsigned long)data);
> + ret = IRQ_HANDLED;
> + }
> +
> + if (iir == UART_IIR_THRI) {
> + hci_h4p_tx_tasklet((unsigned long)data);
> + ret = IRQ_HANDLED;
> + }
> +
> + return ret;
> +}
> +
> +static irqreturn_t hci_h4p_wakeup_interrupt(int irq, void *dev_inst)
> +{
> + struct hci_h4p_info *info = dev_inst;
> + int should_wakeup;
> + struct hci_dev *hdev;
> +
> + if (!info->hdev)
> + return IRQ_HANDLED;
> +
> + should_wakeup = gpio_get_value(info->host_wakeup_gpio);
> + hdev = info->hdev;
> +
> + if (!test_bit(HCI_RUNNING, &hdev->flags)) {
> + if (should_wakeup == 1)
> + complete_all(&info->test_completion);
> +
> + return IRQ_HANDLED;
> + }
> +
> + BT_DBG("gpio interrupt %d", should_wakeup);
> +
> + /* Check if wee have missed some interrupts */
> + if (info->rx_enabled == should_wakeup)
> + return IRQ_HANDLED;
> +
> + if (should_wakeup)
> + hci_h4p_enable_rx(info);
> + else
> + hci_h4p_disable_rx(info);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static inline void hci_h4p_set_pm_limits(struct hci_h4p_info *info, bool set)
> +{
> + struct hci_h4p_platform_data *bt_plat_data = info->dev->platform_data;
> + const char *sset = set ? "set" : "clear";
> +
> + if (unlikely(!bt_plat_data || !bt_plat_data->set_pm_limits))
> + return;
> +
> + if (set != !!test_bit(H4P_ACTIVE_MODE, &info->pm_flags)) {
> + bt_plat_data->set_pm_limits(info->dev, set);
> + if (set)
> + set_bit(H4P_ACTIVE_MODE, &info->pm_flags);
> + else
> + clear_bit(H4P_ACTIVE_MODE, &info->pm_flags);
> + BT_DBG("Change pm constraints to: %s", sset);
> + return;
> + }
> +
> + BT_DBG("pm constraints remains: %s", sset);
> +}
> +
> +static int hci_h4p_reset(struct hci_h4p_info *info)
> +{
> + int err;
> +
> + err = hci_h4p_reset_uart(info);
> + if (err < 0) {
> + dev_err(info->dev, "Uart reset failed\n");
> + return err;
> + }
> + hci_h4p_init_uart(info);
> + hci_h4p_set_rts(info, 0);
> +
> + gpio_set_value(info->reset_gpio, 0);
> + gpio_set_value(info->bt_wakeup_gpio, 1);
> + msleep(10);
> +
> + if (gpio_get_value(info->host_wakeup_gpio) == 1) {
> + dev_err(info->dev, "host_wakeup_gpio not low\n");
> + return -EPROTO;
> + }
> +
> + init_completion(&info->test_completion);
> + gpio_set_value(info->reset_gpio, 1);
> +
> + if (!wait_for_completion_interruptible_timeout(&info->test_completion,
> + msecs_to_jiffies(100))) {
> + dev_err(info->dev, "wakeup test timed out\n");
> + complete_all(&info->test_completion);
> + return -EPROTO;
> + }
> +
> + err = hci_h4p_wait_for_cts(info, 1, 100);
> + if (err < 0) {
> + dev_err(info->dev, "No cts from bt chip\n");
> + return err;
> + }
> +
> + hci_h4p_set_rts(info, 1);
> +
> + return 0;
> +}
> +
> +/* hci callback functions */
> +static int hci_h4p_hci_flush(struct hci_dev *hdev)
> +{
> + struct hci_h4p_info *info = hci_get_drvdata(hdev);
> + skb_queue_purge(&info->txq);
> +
> + return 0;
> +}
> +
> +static int hci_h4p_bt_wakeup_test(struct hci_h4p_info *info)
> +{
> + /*
> + * Test Sequence:
> + * Host de-asserts the BT_WAKE_UP line.
> + * Host polls the UART_CTS line, waiting for it to be de-asserted.
> + * Host asserts the BT_WAKE_UP line.
> + * Host polls the UART_CTS line, waiting for it to be asserted.
> + * Host de-asserts the BT_WAKE_UP line (allow the Bluetooth device to
> + * sleep).
> + * Host polls the UART_CTS line, waiting for it to be de-asserted.
> + */
> + int err;
> + int ret = -ECOMM;
> +
> + if (!info)
> + return -EINVAL;
> +
> + /* Disable wakeup interrupts */
> + disable_irq(gpio_to_irq(info->host_wakeup_gpio));
> +
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + err = hci_h4p_wait_for_cts(info, 0, 100);
> + if (err) {
> + dev_warn(info->dev, "bt_wakeup_test: fail: "
> + "CTS low timed out: %d\n", err);
> + goto out;
> + }
> +
> + gpio_set_value(info->bt_wakeup_gpio, 1);
> + err = hci_h4p_wait_for_cts(info, 1, 100);
> + if (err) {
> + dev_warn(info->dev, "bt_wakeup_test: fail: "
> + "CTS high timed out: %d\n", err);
> + goto out;
> + }
> +
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + err = hci_h4p_wait_for_cts(info, 0, 100);
> + if (err) {
> + dev_warn(info->dev, "bt_wakeup_test: fail: "
> + "CTS re-low timed out: %d\n", err);
> + goto out;
> + }
> +
> + ret = 0;
> +
> +out:
> +
> + /* Re-enable wakeup interrupts */
> + enable_irq(gpio_to_irq(info->host_wakeup_gpio));
> +
> + return ret;
> +}
> +
> +static int hci_h4p_hci_open(struct hci_dev *hdev)
> +{
> + struct hci_h4p_info *info;
> + int err, retries = 0;
> + struct sk_buff_head fw_queue;
> + unsigned long flags;
> +
> + info = hci_get_drvdata(hdev);
> +
> + if (test_bit(HCI_RUNNING, &hdev->flags))
> + return 0;
> +
> + /* TI1271 has HW bug and boot up might fail. Retry up to three times */
> +again:
> +
> + info->rx_enabled = 1;
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + info->rx_count = 0;
> + info->garbage_bytes = 0;
> + info->rx_skb = NULL;
> + info->pm_enabled = 0;
> + init_completion(&info->fw_completion);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 1);
> + skb_queue_head_init(&fw_queue);
> +
> + err = hci_h4p_reset(info);
> + if (err < 0)
> + goto err_clean;
> +
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_CTS | UART_EFR_RTS);
> + info->autorts = 1;
> +
> + err = hci_h4p_send_negotiation(info);
> +
> + err = hci_h4p_read_fw(info, &fw_queue);
> + if (err < 0) {
> + dev_err(info->dev, "Cannot read firmware\n");
> + goto err_clean;
> + }
> +
> + err = hci_h4p_send_fw(info, &fw_queue);
> + if (err < 0) {
> + dev_err(info->dev, "Sending firmware failed.\n");
> + goto err_clean;
> + }
> +
> + info->pm_enabled = 1;
> +
> + err = hci_h4p_bt_wakeup_test(info);
> + if (err < 0) {
> + dev_err(info->dev, "BT wakeup test failed.\n");
> + goto err_clean;
> + }
> +
> + spin_lock_irqsave(&info->lock, flags);
> + info->rx_enabled = gpio_get_value(info->host_wakeup_gpio);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, info->rx_enabled);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> +
> + kfree_skb(info->alive_cmd_skb);
> + info->alive_cmd_skb = NULL;
> + set_bit(HCI_RUNNING, &hdev->flags);
> +
> + BT_DBG("hci up and running");
> + return 0;
> +
> +err_clean:
> + hci_h4p_hci_flush(hdev);
> + hci_h4p_reset_uart(info);
> + del_timer_sync(&info->lazy_release);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> + gpio_set_value(info->reset_gpio, 0);
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + skb_queue_purge(&fw_queue);
> + kfree_skb(info->alive_cmd_skb);
> + info->alive_cmd_skb = NULL;
> + kfree_skb(info->rx_skb);
> + info->rx_skb = NULL;
> +
> + if (retries++ < 3) {
> + dev_err(info->dev, "FW loading try %d fail. Retry.\n", retries);
> + goto again;
> + }
> +
> + return err;
> +}
> +
> +static int hci_h4p_hci_close(struct hci_dev *hdev)
> +{
> + struct hci_h4p_info *info = hci_get_drvdata(hdev);
> +
> + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
> + return 0;
> +
> + hci_h4p_hci_flush(hdev);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 1);
> + hci_h4p_reset_uart(info);
> + del_timer_sync(&info->lazy_release);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> + gpio_set_value(info->reset_gpio, 0);
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + kfree_skb(info->rx_skb);
> +
> + return 0;
> +}
> +
> +static int hci_h4p_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct hci_h4p_info *info;
> + int err = 0;
> +
> + BT_DBG("dev %p, skb %p", hdev, skb);
> +
> + info = hci_get_drvdata(hdev);
> +
> + if (!test_bit(HCI_RUNNING, &hdev->flags)) {
> + dev_warn(info->dev, "Frame for non-running device\n");
> + return -EIO;
> + }
> +
> + switch (bt_cb(skb)->pkt_type) {
> + case HCI_COMMAND_PKT:
> + hdev->stat.cmd_tx++;
> + break;
> + case HCI_ACLDATA_PKT:
> + hdev->stat.acl_tx++;
> + break;
> + case HCI_SCODATA_PKT:
> + hdev->stat.sco_tx++;
> + break;
> + }
> +
> + /* Push frame type to skb */
> + *skb_push(skb, 1) = (bt_cb(skb)->pkt_type);
> + /* We should allways send word aligned data to h4+ devices */
> + if (skb->len % 2) {
> + err = skb_pad(skb, 1);
> + if (!err)
> + *skb_put(skb, 1) = 0x00;
> + }
> + if (err)
> + return err;
> +
> + skb_queue_tail(&info->txq, skb);
> + hci_h4p_enable_tx(info);
> +
> + return 0;
> +}
> +
> +static ssize_t hci_h4p_store_bdaddr(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct hci_h4p_info *info = dev_get_drvdata(dev);
> + unsigned int bdaddr[6];
> + int ret, i;
> +
> + ret = sscanf(buf, "%2x:%2x:%2x:%2x:%2x:%2x\n",
> + &bdaddr[0], &bdaddr[1], &bdaddr[2],
> + &bdaddr[3], &bdaddr[4], &bdaddr[5]);
> +
> + if (ret != 6)
> + return -EINVAL;
> +
> + for (i = 0; i < 6; i++) {
> + if (bdaddr[i] > 0xff)
> + return -EINVAL;
> + info->bd_addr[i] = bdaddr[i] & 0xff;
> + }
> +
> + return count;
> +}
Since none of these devices can function without having a valid address, the way this should work is that we should not register the HCI device when probing the platform device.
The HCI device should be registered once a valid address has been written into the sysfs file. I do not want to play the tricks with bringing up the device without a valid address.
> +
> +static ssize_t hci_h4p_show_bdaddr(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct hci_h4p_info *info = dev_get_drvdata(dev);
> +
> + return sprintf(buf, "%pMR\n", info->bd_addr);
> +}
> +
> +static DEVICE_ATTR(bdaddr, S_IRUGO | S_IWUSR, hci_h4p_show_bdaddr,
> + hci_h4p_store_bdaddr);
> +
> +static int hci_h4p_sysfs_create_files(struct device *dev)
> +{
> + return device_create_file(dev, &dev_attr_bdaddr);
> +}
> +
> +static void hci_h4p_sysfs_remove_files(struct device *dev)
> +{
> + device_remove_file(dev, &dev_attr_bdaddr);
> +}
> +
> +static int hci_h4p_register_hdev(struct hci_h4p_info *info)
> +{
> + struct hci_dev *hdev;
> +
> + /* Initialize and register HCI device */
> +
> + hdev = hci_alloc_dev();
> + if (!hdev) {
> + dev_err(info->dev, "Can't allocate memory for device\n");
> + return -ENOMEM;
> + }
> + info->hdev = hdev;
> +
> + hdev->bus = HCI_UART;
> + hci_set_drvdata(hdev, info);
> +
> + hdev->open = hci_h4p_hci_open;
> + hdev->close = hci_h4p_hci_close;
> + hdev->flush = hci_h4p_hci_flush;
> + hdev->send = hci_h4p_hci_send_frame;
It needs to use hdev->setup to load the firmware. I assume the firmware only needs to be loaded once. That is exactly what hdev->setup does. It gets executed once.
> + set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);
Is this quirk really needed? Normally only Bluetooth 1.1 and early devices qualify for it.
> +
> + SET_HCIDEV_DEV(hdev, info->dev);
> +
> + if (hci_h4p_sysfs_create_files(info->dev) < 0) {
> + dev_err(info->dev, "failed to create sysfs files\n");
> + goto free;
> + }
> +
> + if (hci_register_dev(hdev) >= 0)
> + return 0;
> +
> + dev_err(info->dev, "hci_register failed %s.\n", hdev->name);
> + hci_h4p_sysfs_remove_files(info->dev);
> +free:
> + hci_free_dev(info->hdev);
> + return -ENODEV;
> +}
> +
> +static int hci_h4p_probe(struct platform_device *pdev)
> +{
> + struct hci_h4p_platform_data *bt_plat_data;
> + struct hci_h4p_info *info;
> + int err;
> +
> + dev_info(&pdev->dev, "Registering HCI H4P device\n");
> + info = devm_kzalloc(&pdev->dev, sizeof(struct hci_h4p_info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + info->dev = &pdev->dev;
> + info->tx_enabled = 1;
> + info->rx_enabled = 1;
> + spin_lock_init(&info->lock);
> + spin_lock_init(&info->clocks_lock);
> + skb_queue_head_init(&info->txq);
> +
> + if (pdev->dev.platform_data == NULL) {
> + dev_err(&pdev->dev, "Could not get Bluetooth config data\n");
> + return -ENODATA;
> + }
> +
> + bt_plat_data = pdev->dev.platform_data;
> + info->chip_type = bt_plat_data->chip_type;
> + info->bt_wakeup_gpio = bt_plat_data->bt_wakeup_gpio;
> + info->host_wakeup_gpio = bt_plat_data->host_wakeup_gpio;
> + info->reset_gpio = bt_plat_data->reset_gpio;
> + info->reset_gpio_shared = bt_plat_data->reset_gpio_shared;
> + info->bt_sysclk = bt_plat_data->bt_sysclk;
> +
> + BT_DBG("RESET gpio: %d", info->reset_gpio);
> + BT_DBG("BTWU gpio: %d", info->bt_wakeup_gpio);
> + BT_DBG("HOSTWU gpio: %d", info->host_wakeup_gpio);
> + BT_DBG("sysclk: %d", info->bt_sysclk);
> +
> + init_completion(&info->test_completion);
> + complete_all(&info->test_completion);
> +
> + if (!info->reset_gpio_shared) {
> + err = devm_gpio_request_one(&pdev->dev, info->reset_gpio,
> + GPIOF_OUT_INIT_LOW, "bt_reset");
> + if (err < 0) {
> + dev_err(&pdev->dev, "Cannot get GPIO line %d\n",
> + info->reset_gpio);
> + return err;
> + }
> + }
> +
> + err = devm_gpio_request_one(&pdev->dev, info->bt_wakeup_gpio,
> + GPIOF_OUT_INIT_LOW, "bt_wakeup");
> +
> + if (err < 0) {
> + dev_err(info->dev, "Cannot get GPIO line 0x%d",
> + info->bt_wakeup_gpio);
> + return err;
> + }
> +
> + err = devm_gpio_request_one(&pdev->dev, info->host_wakeup_gpio,
> + GPIOF_DIR_IN, "host_wakeup");
> + if (err < 0) {
> + dev_err(info->dev, "Cannot get GPIO line %d",
> + info->host_wakeup_gpio);
> + return err;
> + }
> +
> + info->irq = bt_plat_data->uart_irq;
> + info->uart_base = devm_ioremap(&pdev->dev, bt_plat_data->uart_base, SZ_2K);
> + info->uart_iclk = devm_clk_get(&pdev->dev, bt_plat_data->uart_iclk);
> + info->uart_fclk = devm_clk_get(&pdev->dev, bt_plat_data->uart_fclk);
> +
> + err = devm_request_irq(&pdev->dev, info->irq, hci_h4p_interrupt, IRQF_DISABLED,
> + "hci_h4p", info);
> + if (err < 0) {
> + dev_err(info->dev, "hci_h4p: unable to get IRQ %d\n", info->irq);
> + return err;
> + }
> +
> + err = devm_request_irq(&pdev->dev, gpio_to_irq(info->host_wakeup_gpio),
> + hci_h4p_wakeup_interrupt, IRQF_TRIGGER_FALLING |
> + IRQF_TRIGGER_RISING | IRQF_DISABLED,
> + "hci_h4p_wkup", info);
> + if (err < 0) {
> + dev_err(info->dev, "hci_h4p: unable to get wakeup IRQ %d\n",
> + gpio_to_irq(info->host_wakeup_gpio));
> + return err;
> + }
> +
> + err = irq_set_irq_wake(gpio_to_irq(info->host_wakeup_gpio), 1);
> + if (err < 0) {
> + dev_err(info->dev, "hci_h4p: unable to set wakeup for IRQ %d\n",
> + gpio_to_irq(info->host_wakeup_gpio));
> + return err;
> + }
> +
> + init_timer_deferrable(&info->lazy_release);
> + info->lazy_release.function = hci_h4p_lazy_clock_release;
> + info->lazy_release.data = (unsigned long)info;
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + err = hci_h4p_reset_uart(info);
> + if (err < 0)
> + return err;
> + gpio_set_value(info->reset_gpio, 0);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> +
> + platform_set_drvdata(pdev, info);
> +
> + if (hci_h4p_register_hdev(info) < 0) {
> + dev_err(info->dev, "failed to register hci_h4p hci device\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int hci_h4p_remove(struct platform_device *pdev)
> +{
> + struct hci_h4p_info *info;
> +
> + info = platform_get_drvdata(pdev);
> +
> + hci_h4p_sysfs_remove_files(info->dev);
> + hci_h4p_hci_close(info->hdev);
> + hci_unregister_dev(info->hdev);
> + hci_free_dev(info->hdev);
> +
> + return 0;
> +}
> +
> +static struct platform_driver hci_h4p_driver = {
> + .probe = hci_h4p_probe,
> + .remove = hci_h4p_remove,
> + .driver = {
> + .name = "hci_h4p",
> + },
> +};
> +
> +module_platform_driver(hci_h4p_driver);
> +
> +MODULE_ALIAS("platform:hci_h4p");
> +MODULE_DESCRIPTION("Bluetooth h4 driver with nokia extensions");
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Ville Tervo");
> +MODULE_FIRMWARE(FW_NAME_TI1271_PRELE);
> +MODULE_FIRMWARE(FW_NAME_TI1271_LE);
> +MODULE_FIRMWARE(FW_NAME_TI1271);
> +MODULE_FIRMWARE(FW_NAME_BCM2048);
> +MODULE_FIRMWARE(FW_NAME_CSR);
> diff --git a/drivers/bluetooth/nokia_fw-bcm.c b/drivers/bluetooth/nokia_fw-bcm.c
> new file mode 100644
> index 0000000..e8912bf
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw-bcm.c
> @@ -0,0 +1,147 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/delay.h>
> +#include <linux/serial_reg.h>
> +
> +#include "hci_h4p.h"
> +
> +static int hci_h4p_bcm_set_bdaddr(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + int i;
> + static const u8 nokia_oui[3] = {0x00, 0x1f, 0xdf};
> + int not_valid;
Has this actually been confirmed that we can just randomly set an address out of the Nokia range. I do not think so. This is a pretty bad idea.
I have no interest in merging a driver with such a hack.
> +
> + not_valid = 1;
> + for (i = 0; i < 6; i++) {
> + if (info->bd_addr[i] != 0x00) {
> + not_valid = 0;
> + break;
> + }
> + }
Anybody every heard of memcmp or bacmp and BDADDR_ANY?
> +
> + if (not_valid) {
> + dev_info(info->dev, "Valid bluetooth address not found, setting some random\n");
> + /* When address is not valid, use some random but Nokia MAC */
> + memcpy(info->bd_addr, nokia_oui, 3);
> + get_random_bytes(info->bd_addr + 3, 3);
> + }
> +
> + for (i = 0; i < 6; i++)
> + skb->data[9 - i] = info->bd_addr[i];
> +
> + return 0;
> +}
> +
> +void hci_h4p_bcm_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + struct sk_buff *fw_skb;
> + int err;
> + unsigned long flags;
> +
> + if (skb->data[5] != 0x00) {
> + dev_err(info->dev, "Firmware sending command failed 0x%.2x\n",
> + skb->data[5]);
> + info->fw_error = -EPROTO;
> + }
> +
> + kfree_skb(skb);
> +
> + fw_skb = skb_dequeue(info->fw_q);
> + if (fw_skb == NULL || info->fw_error) {
> + complete(&info->fw_completion);
> + return;
> + }
> +
> + if (fw_skb->data[1] == 0x01 && fw_skb->data[2] == 0xfc && fw_skb->len >= 10) {
> + BT_DBG("Setting bluetooth address");
> + err = hci_h4p_bcm_set_bdaddr(info, fw_skb);
> + if (err < 0) {
> + kfree_skb(fw_skb);
> + info->fw_error = err;
> + complete(&info->fw_completion);
> + return;
> + }
> + }
> +
> + skb_queue_tail(&info->txq, fw_skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +
> +int hci_h4p_bcm_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue)
> +{
> + struct sk_buff *skb;
> + unsigned long flags, time;
> +
> + info->fw_error = 0;
> +
> + BT_DBG("Sending firmware");
> +
> + time = jiffies;
> +
> + info->fw_q = fw_queue;
> + skb = skb_dequeue(fw_queue);
> + if (!skb)
> + return -ENODATA;
> +
> + BT_DBG("Sending commands");
> +
> + /*
> + * Disable smart-idle as UART TX interrupts
> + * are not wake-up capable
> + */
> + hci_h4p_smart_idle(info, 0);
> +
> + /* Check if this is bd_address packet */
> + init_completion(&info->fw_completion);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + if (!wait_for_completion_timeout(&info->fw_completion,
> + msecs_to_jiffies(2000))) {
> + dev_err(info->dev, "No reply to fw command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->fw_error) {
> + dev_err(info->dev, "FW error\n");
> + return -EPROTO;
> + }
> +
> + BT_DBG("Firmware sent in %d msecs",
> + jiffies_to_msecs(jiffies-time));
> +
> + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + hci_h4p_set_rts(info, 0);
> + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE);
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> +
> + return 0;
> +}
> diff --git a/drivers/bluetooth/nokia_fw-csr.c b/drivers/bluetooth/nokia_fw-csr.c
> new file mode 100644
> index 0000000..e39c4a3
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw-csr.c
> @@ -0,0 +1,150 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/delay.h>
> +#include <linux/serial_reg.h>
> +
> +#include "hci_h4p.h"
> +
> +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + /* Check if this is fw packet */
> + if (skb->data[0] != 0xff) {
> + hci_recv_frame(info->hdev, skb);
> + return;
> + }
> +
> + if (skb->data[11] || skb->data[12]) {
> + dev_err(info->dev, "Firmware sending command failed\n");
> + info->fw_error = -EPROTO;
> + }
> +
> + kfree_skb(skb);
> + complete(&info->fw_completion);
> +}
> +
> +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue)
> +{
> + static const u8 nokia_oui[3] = {0x00, 0x19, 0x4F};
> + struct sk_buff *skb;
> + unsigned int offset;
> + int retries, count, i, not_valid;
> + unsigned long flags;
> +
> + info->fw_error = 0;
> +
> + BT_DBG("Sending firmware");
> + skb = skb_dequeue(fw_queue);
> +
> + if (!skb)
> + return -ENOMSG;
> +
> + /* Check if this is bd_address packet */
> + if (skb->data[15] == 0x01 && skb->data[16] == 0x00) {
> + offset = 21;
> + skb->data[offset + 1] = 0x00;
> + skb->data[offset + 5] = 0x00;
> +
> + not_valid = 1;
> + for (i = 0; i < 6; i++) {
> + if (info->bd_addr[i] != 0x00) {
> + not_valid = 0;
> + break;
> + }
> + }
> +
> + if (not_valid) {
> + dev_info(info->dev, "Valid bluetooth address not found,"
> + " setting some random\n");
> + /* When address is not valid, use some random */
> + memcpy(info->bd_addr, nokia_oui, 3);
> + get_random_bytes(info->bd_addr + 3, 3);
> + }
And why does every single chip firmware does this differently. Seriously, this is a mess.
> +
> + skb->data[offset + 7] = info->bd_addr[0];
> + skb->data[offset + 6] = info->bd_addr[1];
> + skb->data[offset + 4] = info->bd_addr[2];
> + skb->data[offset + 0] = info->bd_addr[3];
> + skb->data[offset + 3] = info->bd_addr[4];
> + skb->data[offset + 2] = info->bd_addr[5];
> + }
> +
> + for (count = 1; ; count++) {
> + BT_DBG("Sending firmware command %d", count);
> + init_completion(&info->fw_completion);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + skb = skb_dequeue(fw_queue);
> + if (!skb)
> + break;
> +
> + if (!wait_for_completion_timeout(&info->fw_completion,
> + msecs_to_jiffies(1000))) {
> + dev_err(info->dev, "No reply to fw command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->fw_error) {
> + dev_err(info->dev, "FW error\n");
> + return -EPROTO;
> + }
> + };
> +
> + /* Wait for chip warm reset */
> + retries = 100;
> + while ((!skb_queue_empty(&info->txq) ||
> + !(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT)) &&
> + retries--) {
> + msleep(10);
> + }
> + if (!retries) {
> + dev_err(info->dev, "Transmitter not empty\n");
> + return -ETIMEDOUT;
> + }
> +
> + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE);
> +
> + if (hci_h4p_wait_for_cts(info, 1, 100)) {
> + dev_err(info->dev, "cts didn't deassert after final speed\n");
> + return -ETIMEDOUT;
> + }
> +
> + retries = 100;
> + do {
> + init_completion(&info->init_completion);
> + hci_h4p_send_alive_packet(info);
> + retries--;
> + } while (!wait_for_completion_timeout(&info->init_completion, 100) &&
> + retries > 0);
> +
> + if (!retries) {
> + dev_err(info->dev, "No alive reply after speed change\n");
> + return -ETIMEDOUT;
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/bluetooth/nokia_fw-ti1273.c b/drivers/bluetooth/nokia_fw-ti1273.c
> new file mode 100644
> index 0000000..f5500f7
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw-ti1273.c
> @@ -0,0 +1,110 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2009 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/delay.h>
> +#include <linux/serial_reg.h>
> +
> +#include "hci_h4p.h"
> +
> +static struct sk_buff_head *fw_q;
> +
> +void hci_h4p_ti1273_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + struct sk_buff *fw_skb;
> + unsigned long flags;
> +
> + if (skb->data[5] != 0x00) {
> + dev_err(info->dev, "Firmware sending command failed 0x%.2x\n",
> + skb->data[5]);
> + info->fw_error = -EPROTO;
> + }
> +
> + kfree_skb(skb);
> +
> + fw_skb = skb_dequeue(fw_q);
> + if (fw_skb == NULL || info->fw_error) {
> + complete(&info->fw_completion);
> + return;
> + }
> +
> + skb_queue_tail(&info->txq, fw_skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +
> +int hci_h4p_ti1273_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue)
> +{
> + struct sk_buff *skb;
> + unsigned long flags, time;
> +
> + info->fw_error = 0;
> +
> + BT_DBG("Sending firmware");
> +
> + time = jiffies;
> +
> + fw_q = fw_queue;
> + skb = skb_dequeue(fw_queue);
> + if (!skb)
> + return -ENODATA;
> +
> + BT_DBG("Sending commands");
> + /* Check if this is bd_address packet */
> + init_completion(&info->fw_completion);
> + hci_h4p_smart_idle(info, 0);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + if (!wait_for_completion_timeout(&info->fw_completion,
> + msecs_to_jiffies(2000))) {
> + dev_err(info->dev, "No reply to fw command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->fw_error) {
> + dev_err(info->dev, "FW error\n");
> + return -EPROTO;
> + }
> +
> + BT_DBG("Firmware sent in %d msecs",
> + jiffies_to_msecs(jiffies-time));
> +
> + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + hci_h4p_set_rts(info, 0);
> + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE);
> + if (hci_h4p_wait_for_cts(info, 1, 100)) {
> + dev_err(info->dev,
> + "cts didn't go down after final speed change\n");
> + return -ETIMEDOUT;
> + }
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> +
> + return 0;
> +}
> diff --git a/drivers/bluetooth/nokia_fw.c b/drivers/bluetooth/nokia_fw.c
> new file mode 100644
> index 0000000..cfea61c
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw.c
> @@ -0,0 +1,195 @@
> +/*
> + * This file is part of hci_h4p bluetooth driver
> + *
> + * Copyright (C) 2005, 2006 Nokia Corporation.
> + *
> + * Contact: Ville Tervo <ville.tervo@nokia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/firmware.h>
> +#include <linux/clk.h>
> +
> +#include <net/bluetooth/bluetooth.h>
> +
> +#include "hci_h4p.h"
> +
> +static int fw_pos;
> +
> +/* Firmware handling */
> +static int hci_h4p_open_firmware(struct hci_h4p_info *info,
> + const struct firmware **fw_entry)
> +{
> + int err;
> +
> + fw_pos = 0;
> + BT_DBG("Opening firmware man_id 0x%.2x ver_id 0x%.2x",
> + info->man_id, info->ver_id);
> + switch (info->man_id) {
> + case H4P_ID_TI1271:
> + switch (info->ver_id) {
> + case 0xe1:
> + err = request_firmware(fw_entry, FW_NAME_TI1271_PRELE,
> + info->dev);
> + break;
> + case 0xd1:
> + case 0xf1:
> + err = request_firmware(fw_entry, FW_NAME_TI1271_LE,
> + info->dev);
> + break;
> + default:
> + err = request_firmware(fw_entry, FW_NAME_TI1271,
> + info->dev);
> + }
> + break;
> + case H4P_ID_CSR:
> + err = request_firmware(fw_entry, FW_NAME_CSR, info->dev);
> + break;
> + case H4P_ID_BCM2048:
> + err = request_firmware(fw_entry, FW_NAME_BCM2048, info->dev);
> + break;
> + default:
> + dev_err(info->dev, "Invalid chip type\n");
> + *fw_entry = NULL;
> + err = -EINVAL;
> + }
> +
> + return err;
> +}
> +
> +static void hci_h4p_close_firmware(const struct firmware *fw_entry)
> +{
> + release_firmware(fw_entry);
> +}
> +
> +/* Read fw. Return length of the command. If no more commands in
> + * fw 0 is returned. In error case return value is negative.
> + */
> +static int hci_h4p_read_fw_cmd(struct hci_h4p_info *info, struct sk_buff **skb,
> + const struct firmware *fw_entry, gfp_t how)
> +{
> + unsigned int cmd_len;
> +
> + if (fw_pos >= fw_entry->size)
> + return 0;
> +
> + if (fw_pos + 2 > fw_entry->size) {
> + dev_err(info->dev, "Corrupted firmware image 1\n");
> + return -EMSGSIZE;
> + }
> +
> + cmd_len = fw_entry->data[fw_pos++];
> + cmd_len += fw_entry->data[fw_pos++] << 8;
> + if (cmd_len == 0)
> + return 0;
> +
> + if (fw_pos + cmd_len > fw_entry->size) {
> + dev_err(info->dev, "Corrupted firmware image 2\n");
> + return -EMSGSIZE;
> + }
> +
> + *skb = bt_skb_alloc(cmd_len, how);
> + if (!*skb) {
> + dev_err(info->dev, "Cannot reserve memory for buffer\n");
> + return -ENOMEM;
> + }
> + memcpy(skb_put(*skb, cmd_len), &fw_entry->data[fw_pos], cmd_len);
> +
> + fw_pos += cmd_len;
> +
> + return (*skb)->len;
> +}
> +
> +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue)
> +{
> + const struct firmware *fw_entry = NULL;
> + struct sk_buff *skb = NULL;
> + int err;
> +
> + err = hci_h4p_open_firmware(info, &fw_entry);
> + if (err < 0 || !fw_entry)
> + goto err_clean;
> +
> + while ((err = hci_h4p_read_fw_cmd(info, &skb, fw_entry, GFP_KERNEL))) {
> + if (err < 0 || !skb)
> + goto err_clean;
> +
> + skb_queue_tail(fw_queue, skb);
> + }
> +
> + /* Chip detection code does neg and alive stuff
> + * discard two first skbs */
> + skb = skb_dequeue(fw_queue);
> + if (!skb) {
> + err = -EMSGSIZE;
> + goto err_clean;
> + }
> + kfree_skb(skb);
> + skb = skb_dequeue(fw_queue);
> + if (!skb) {
> + err = -EMSGSIZE;
> + goto err_clean;
> + }
> + kfree_skb(skb);
> +
> +err_clean:
> + hci_h4p_close_firmware(fw_entry);
> + return err;
> +}
> +
> +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue)
> +{
> + int err;
> +
> + switch (info->man_id) {
> + case H4P_ID_CSR:
> + err = hci_h4p_bc4_send_fw(info, fw_queue);
> + break;
> + case H4P_ID_TI1271:
> + err = hci_h4p_ti1273_send_fw(info, fw_queue);
> + break;
> + case H4P_ID_BCM2048:
> + err = hci_h4p_bcm_send_fw(info, fw_queue);
> + break;
> + default:
> + dev_err(info->dev, "Don't know how to send firmware\n");
> + err = -EINVAL;
> + }
> +
> + return err;
> +}
> +
> +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + switch (info->man_id) {
> + case H4P_ID_CSR:
> + hci_h4p_bc4_parse_fw_event(info, skb);
> + break;
> + case H4P_ID_TI1271:
> + hci_h4p_ti1273_parse_fw_event(info, skb);
> + break;
> + case H4P_ID_BCM2048:
> + hci_h4p_bcm_parse_fw_event(info, skb);
> + break;
> + default:
> + dev_err(info->dev, "Don't know how to parse fw event\n");
> + info->fw_error = -EINVAL;
> + }
> +
> + return;
> +}
We have proper HCI sync command handling in recent kernels. I really do not know why this is hand coded these days. Check how the Intel firmware loading inside btusb.c does it.
> diff --git a/drivers/bluetooth/nokia_uart.c b/drivers/bluetooth/nokia_uart.c
> new file mode 100644
> index 0000000..0fb57de
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_uart.c
> @@ -0,0 +1,199 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005, 2006 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/serial_reg.h>
> +#include <linux/delay.h>
> +#include <linux/clk.h>
> +
> +#include <linux/io.h>
> +
> +#include "hci_h4p.h"
> +
> +inline void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val)
> +{
> + __raw_writeb(val, info->uart_base + (offset << 2));
> +}
> +
> +inline u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset)
> +{
> + return __raw_readb(info->uart_base + (offset << 2));
> +}
Inline in a *.c file for a non-static function. Makes no sense to me.
> +
> +void hci_h4p_set_rts(struct hci_h4p_info *info, int active)
> +{
> + u8 b;
> +
> + b = hci_h4p_inb(info, UART_MCR);
> + if (active)
> + b |= UART_MCR_RTS;
> + else
> + b &= ~UART_MCR_RTS;
> + hci_h4p_outb(info, UART_MCR, b);
> +}
> +
> +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active,
> + int timeout_ms)
> +{
> + unsigned long timeout;
> + int state;
> +
> + timeout = jiffies + msecs_to_jiffies(timeout_ms);
> + for (;;) {
> + state = hci_h4p_inb(info, UART_MSR) & UART_MSR_CTS;
> + if (active) {
> + if (state)
> + return 0;
> + } else {
> + if (!state)
> + return 0;
> + }
> + if (time_after(jiffies, timeout))
> + return -ETIMEDOUT;
> + msleep(1);
> + }
> +}
> +
> +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which)
> +{
> + u8 lcr, b;
> +
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, 0xbf);
> + b = hci_h4p_inb(info, UART_EFR);
> + if (on)
> + b |= which;
> + else
> + b &= ~which;
> + hci_h4p_outb(info, UART_EFR, b);
> + hci_h4p_outb(info, UART_LCR, lcr);
> +}
> +
> +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&info->lock, flags);
> + __hci_h4p_set_auto_ctsrts(info, on, which);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed)
> +{
> + unsigned int divisor;
> + u8 lcr, mdr1;
> +
> + BT_DBG("Setting speed %lu", speed);
> +
> + if (speed >= 460800) {
> + divisor = UART_CLOCK / 13 / speed;
> + mdr1 = 3;
> + } else {
> + divisor = UART_CLOCK / 16 / speed;
> + mdr1 = 0;
> + }
> +
> + /* Make sure UART mode is disabled */
> + hci_h4p_outb(info, UART_OMAP_MDR1, 7);
> +
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB); /* Set DLAB */
> + hci_h4p_outb(info, UART_DLL, divisor & 0xff); /* Set speed */
> + hci_h4p_outb(info, UART_DLM, divisor >> 8);
> + hci_h4p_outb(info, UART_LCR, lcr);
> +
> + /* Make sure UART mode is enabled */
> + hci_h4p_outb(info, UART_OMAP_MDR1, mdr1);
> +}
> +
> +int hci_h4p_reset_uart(struct hci_h4p_info *info)
> +{
> + int count = 0;
> +
> + /* Reset the UART */
> + hci_h4p_outb(info, UART_OMAP_SYSC, UART_SYSC_OMAP_RESET);
> + while (!(hci_h4p_inb(info, UART_OMAP_SYSS) & UART_SYSS_RESETDONE)) {
> + if (count++ > 100) {
> + dev_err(info->dev, "hci_h4p: UART reset timeout\n");
> + return -ENODEV;
> + }
> + udelay(1);
> + }
> +
> + return 0;
> +}
> +
> +void hci_h4p_store_regs(struct hci_h4p_info *info)
> +{
> + u16 lcr = 0;
> +
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, 0xBF);
> + info->dll = hci_h4p_inb(info, UART_DLL);
> + info->dlh = hci_h4p_inb(info, UART_DLM);
> + info->efr = hci_h4p_inb(info, UART_EFR);
> + hci_h4p_outb(info, UART_LCR, lcr);
> + info->mdr1 = hci_h4p_inb(info, UART_OMAP_MDR1);
> + info->ier = hci_h4p_inb(info, UART_IER);
> +}
> +
> +void hci_h4p_restore_regs(struct hci_h4p_info *info)
> +{
> + u16 lcr = 0;
> +
> + hci_h4p_init_uart(info);
> +
> + hci_h4p_outb(info, UART_OMAP_MDR1, 7);
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, 0xBF);
> + hci_h4p_outb(info, UART_DLL, info->dll); /* Set speed */
> + hci_h4p_outb(info, UART_DLM, info->dlh);
> + hci_h4p_outb(info, UART_EFR, info->efr);
> + hci_h4p_outb(info, UART_LCR, lcr);
> + hci_h4p_outb(info, UART_OMAP_MDR1, info->mdr1);
> + hci_h4p_outb(info, UART_IER, info->ier);
> +}
> +
> +void hci_h4p_init_uart(struct hci_h4p_info *info)
> +{
> + u8 mcr, efr;
> +
> + /* Enable and setup FIFO */
> + hci_h4p_outb(info, UART_OMAP_MDR1, 0x00);
> +
> + hci_h4p_outb(info, UART_LCR, 0xbf);
> + efr = hci_h4p_inb(info, UART_EFR);
> + hci_h4p_outb(info, UART_EFR, UART_EFR_ECB);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB);
> + mcr = hci_h4p_inb(info, UART_MCR);
> + hci_h4p_outb(info, UART_MCR, UART_MCR_TCRTLR);
> + hci_h4p_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO |
> + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT |
> + (3 << 6) | (0 << 4));
> + hci_h4p_outb(info, UART_LCR, 0xbf);
> + hci_h4p_outb(info, UART_TI752_TLR, 0xed);
> + hci_h4p_outb(info, UART_TI752_TCR, 0xef);
> + hci_h4p_outb(info, UART_EFR, efr);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB);
> + hci_h4p_outb(info, UART_MCR, 0x00);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_WLEN8);
> + hci_h4p_outb(info, UART_IER, UART_IER_RDI);
> + hci_h4p_outb(info, UART_OMAP_SYSC, (1 << 0) | (1 << 2) | (2 << 3));
> +}
> diff --git a/include/linux/platform_data/hci-h4p.h b/include/linux/platform_data/hci-h4p.h
> new file mode 100644
> index 0000000..30d169d
> --- /dev/null
> +++ b/include/linux/platform_data/hci-h4p.h
> @@ -0,0 +1,38 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2010 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +
> +/**
> + * struct hci_h4p_platform data - hci_h4p Platform data structure
> + */
> +struct hci_h4p_platform_data {
please have a proper name here. For example btnokia_h4p_platform_data. Same applies to the file name.
> + int chip_type;
> + int bt_sysclk;
> + unsigned int bt_wakeup_gpio;
> + unsigned int host_wakeup_gpio;
> + unsigned int reset_gpio;
> + int reset_gpio_shared;
> + unsigned int uart_irq;
> + phys_addr_t uart_base;
> + const char *uart_iclk;
> + const char *uart_fclk;
> + void (*set_pm_limits)(struct device *dev, bool set);
> +};
Regards
Marcel
^ permalink raw reply
* Re: [PATCH v6] Bluetooth: Add hci_h4p driver
From: Pavel Machek @ 2014-01-16 0:22 UTC (permalink / raw)
To: Marcel Holtmann
Cc: Pali Rohár,
Ивайло Димитров,
Gustavo F. Padovan, Johan Hedberg, linux-kernel,
linux-bluetooth@vger.kernel.org development, Ville Tervo,
Sebastian Reichel
In-Reply-To: <20140111002801.GA20216@amd.pavel.ucw.cz>
Hi!
Ping? I did see it in bluetooth-next. Could we get it applied?
Thanks,
Pavel
On Sat 2014-01-11 01:28:01, Pavel Machek wrote:
> Add hci_h4p bluetooth driver to bluetooth-next. This device is used
> for example on Nokia N900 cell phone.
>
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> Thanks-to: Sebastian Reichel <sre@debian.org>
> Thanks-to: Joe Perches <joe@perches.com>
>
> ---
>
> Changes from v5: Comment fixes and some refactoring suggested by
> Joe Perches. Please apply.
>
> diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
> index 11a6104..a53e8c7 100644
> --- a/drivers/bluetooth/Kconfig
> +++ b/drivers/bluetooth/Kconfig
> @@ -241,5 +241,15 @@ config BT_WILINK
> core driver to communicate with the BT core of the combo chip.
>
> Say Y here to compile support for Texas Instrument's WiLink7 driver
> - into the kernel or say M to compile it as module.
> + into the kernel or say M to compile it as module (btwilink).
> +
> +config BT_NOKIA_H4P
> + tristate "HCI driver with H4 Nokia extensions"
> + depends on BT && ARCH_OMAP
> + help
> + Bluetooth HCI driver with H4 extensions. This driver provides
> + support for H4+ Bluetooth chip with vendor-specific H4 extensions.
> +
> + Say Y here to compile support for h4 extended devices into the kernel
> + or say M to compile it as module (btnokia_h4p).
> endmenu
> diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
> index 9fe8a87..6f25db2 100644
> --- a/drivers/bluetooth/Makefile
> +++ b/drivers/bluetooth/Makefile
> @@ -31,4 +31,8 @@ hci_uart-$(CONFIG_BT_HCIUART_ATH3K) += hci_ath.o
> hci_uart-$(CONFIG_BT_HCIUART_3WIRE) += hci_h5.o
> hci_uart-objs := $(hci_uart-y)
>
> +obj-$(CONFIG_BT_NOKIA_H4P) += btnokia_h4p.o
> +btnokia_h4p-objs := nokia_core.o nokia_fw.o nokia_uart.o nokia_fw-csr.o \
> + nokia_fw-bcm.o nokia_fw-ti1273.o
> +
> ccflags-y += -D__CHECK_ENDIAN__
> diff --git a/drivers/bluetooth/hci_h4p.h b/drivers/bluetooth/hci_h4p.h
> new file mode 100644
> index 0000000..fd7a640
> --- /dev/null
> +++ b/drivers/bluetooth/hci_h4p.h
> @@ -0,0 +1,228 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#ifndef __DRIVERS_BLUETOOTH_HCI_H4P_H
> +#define __DRIVERS_BLUETOOTH_HCI_H4P_H
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +#include <net/bluetooth/hci.h>
> +
> +#define FW_NAME_TI1271_PRELE "ti1273_prele.bin"
> +#define FW_NAME_TI1271_LE "ti1273_le.bin"
> +#define FW_NAME_TI1271 "ti1273.bin"
> +#define FW_NAME_BCM2048 "bcmfw.bin"
> +#define FW_NAME_CSR "bc4fw.bin"
> +
> +#define UART_SYSC_OMAP_RESET 0x03
> +#define UART_SYSS_RESETDONE 0x01
> +#define UART_OMAP_SCR_EMPTY_THR 0x08
> +#define UART_OMAP_SCR_WAKEUP 0x10
> +#define UART_OMAP_SSR_WAKEUP 0x02
> +#define UART_OMAP_SSR_TXFULL 0x01
> +
> +#define UART_OMAP_SYSC_IDLEMODE 0x03
> +#define UART_OMAP_SYSC_IDLEMASK (3 << UART_OMAP_SYSC_IDLEMODE)
> +
> +#define UART_OMAP_SYSC_FORCE_IDLE (0 << UART_OMAP_SYSC_IDLEMODE)
> +#define UART_OMAP_SYSC_NO_IDLE (1 << UART_OMAP_SYSC_IDLEMODE)
> +#define UART_OMAP_SYSC_SMART_IDLE (2 << UART_OMAP_SYSC_IDLEMODE)
> +
> +#define H4P_TRANSFER_MODE 1
> +#define H4P_SCHED_TRANSFER_MODE 2
> +#define H4P_ACTIVE_MODE 3
> +
> +struct hci_h4p_info {
> + struct timer_list lazy_release;
> + struct hci_dev *hdev;
> + spinlock_t lock;
> +
> + void __iomem *uart_base;
> + unsigned long uart_phys_base;
> + int irq;
> + struct device *dev;
> + u8 chip_type;
> + u8 bt_wakeup_gpio;
> + u8 host_wakeup_gpio;
> + u8 reset_gpio;
> + u8 reset_gpio_shared;
> + u8 bt_sysclk;
> + u8 man_id;
> + u8 ver_id;
> +
> + struct sk_buff_head fw_queue;
> + struct sk_buff *alive_cmd_skb;
> + struct completion init_completion;
> + struct completion fw_completion;
> + struct completion test_completion;
> + int fw_error;
> + int init_error;
> +
> + struct sk_buff_head txq;
> +
> + struct sk_buff *rx_skb;
> + long rx_count;
> + unsigned long rx_state;
> + unsigned long garbage_bytes;
> +
> + u8 bd_addr[6];
> + struct sk_buff_head *fw_q;
> +
> + int pm_enabled;
> + int tx_enabled;
> + int autorts;
> + int rx_enabled;
> + unsigned long pm_flags;
> +
> + int tx_clocks_en;
> + int rx_clocks_en;
> + spinlock_t clocks_lock;
> + struct clk *uart_iclk;
> + struct clk *uart_fclk;
> + atomic_t clk_users;
> + u16 dll;
> + u16 dlh;
> + u16 ier;
> + u16 mdr1;
> + u16 efr;
> +};
> +
> +struct hci_h4p_radio_hdr {
> + __u8 evt;
> + __u8 dlen;
> +} __attribute__ ((packed));
> +
> +struct hci_h4p_neg_hdr {
> + __u8 dlen;
> +} __attribute__ ((packed));
> +#define H4P_NEG_HDR_SIZE 1
> +
> +#define H4P_NEG_REQ 0x00
> +#define H4P_NEG_ACK 0x20
> +#define H4P_NEG_NAK 0x40
> +
> +#define H4P_PROTO_PKT 0x44
> +#define H4P_PROTO_BYTE 0x4c
> +
> +#define H4P_ID_CSR 0x02
> +#define H4P_ID_BCM2048 0x04
> +#define H4P_ID_TI1271 0x31
> +
> +struct hci_h4p_neg_cmd {
> + __u8 ack;
> + __u16 baud;
> + __u16 unused1;
> + __u8 proto;
> + __u16 sys_clk;
> + __u16 unused2;
> +} __attribute__ ((packed));
> +
> +struct hci_h4p_neg_evt {
> + __u8 ack;
> + __u16 baud;
> + __u16 unused1;
> + __u8 proto;
> + __u16 sys_clk;
> + __u16 unused2;
> + __u8 man_id;
> + __u8 ver_id;
> +} __attribute__ ((packed));
> +
> +#define H4P_ALIVE_REQ 0x55
> +#define H4P_ALIVE_RESP 0xcc
> +
> +struct hci_h4p_alive_hdr {
> + __u8 dlen;
> +} __attribute__ ((packed));
> +#define H4P_ALIVE_HDR_SIZE 1
> +
> +struct hci_h4p_alive_pkt {
> + __u8 mid;
> + __u8 unused;
> +} __attribute__ ((packed));
> +
> +#define MAX_BAUD_RATE 921600
> +#define BC4_MAX_BAUD_RATE 3692300
> +#define UART_CLOCK 48000000
> +#define BT_INIT_DIVIDER 320
> +#define BT_BAUDRATE_DIVIDER 384000000
> +#define BT_SYSCLK_DIV 1000
> +#define INIT_SPEED 120000
> +
> +#define H4_TYPE_SIZE 1
> +#define H4_RADIO_HDR_SIZE 2
> +
> +/* H4+ packet types */
> +#define H4_CMD_PKT 0x01
> +#define H4_ACL_PKT 0x02
> +#define H4_SCO_PKT 0x03
> +#define H4_EVT_PKT 0x04
> +#define H4_NEG_PKT 0x06
> +#define H4_ALIVE_PKT 0x07
> +#define H4_RADIO_PKT 0x08
> +
> +/* TX states */
> +#define WAIT_FOR_PKT_TYPE 1
> +#define WAIT_FOR_HEADER 2
> +#define WAIT_FOR_DATA 3
> +
> +struct hci_fw_event {
> + struct hci_event_hdr hev;
> + struct hci_ev_cmd_complete cmd;
> + u8 status;
> +} __attribute__ ((packed));
> +
> +int hci_h4p_send_alive_packet(struct hci_h4p_info *info);
> +
> +void hci_h4p_bcm_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb);
> +int hci_h4p_bcm_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue);
> +
> +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb);
> +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue);
> +
> +void hci_h4p_ti1273_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb);
> +int hci_h4p_ti1273_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue);
> +
> +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue);
> +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue);
> +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb);
> +
> +void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val);
> +u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset);
> +void hci_h4p_set_rts(struct hci_h4p_info *info, int active);
> +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, int timeout_ms);
> +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which);
> +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which);
> +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed);
> +int hci_h4p_reset_uart(struct hci_h4p_info *info);
> +void hci_h4p_init_uart(struct hci_h4p_info *info);
> +void hci_h4p_enable_tx(struct hci_h4p_info *info);
> +void hci_h4p_store_regs(struct hci_h4p_info *info);
> +void hci_h4p_restore_regs(struct hci_h4p_info *info);
> +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable);
> +
> +#endif /* __DRIVERS_BLUETOOTH_HCI_H4P_H */
> diff --git a/drivers/bluetooth/nokia_core.c b/drivers/bluetooth/nokia_core.c
> new file mode 100644
> index 0000000..54ec594
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_core.c
> @@ -0,0 +1,1205 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + * Thanks to all the Nokia people that helped with this driver,
> + * including Ville Tervo and Roger Quadros.
> + *
> + * Power saving functionality was removed from this driver to make
> + * merging easier.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/errno.h>
> +#include <linux/delay.h>
> +#include <linux/spinlock.h>
> +#include <linux/serial_reg.h>
> +#include <linux/skbuff.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/gpio.h>
> +#include <linux/timer.h>
> +#include <linux/kthread.h>
> +#include <linux/io.h>
> +#include <linux/completion.h>
> +#include <linux/sizes.h>
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +#include <net/bluetooth/hci.h>
> +
> +#include <linux/platform_data/hci-h4p.h>
> +
> +#include "hci_h4p.h"
> +
> +/* This should be used in function that cannot release clocks */
> +static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&info->clocks_lock, flags);
> + if (enable && !*clock) {
> + BT_DBG("Enabling %p", clock);
> + clk_prepare_enable(info->uart_fclk);
> + clk_prepare_enable(info->uart_iclk);
> + if (atomic_read(&info->clk_users) == 0)
> + hci_h4p_restore_regs(info);
> + atomic_inc(&info->clk_users);
> + }
> +
> + if (!enable && *clock) {
> + BT_DBG("Disabling %p", clock);
> + if (atomic_dec_and_test(&info->clk_users))
> + hci_h4p_store_regs(info);
> + clk_disable_unprepare(info->uart_fclk);
> + clk_disable_unprepare(info->uart_iclk);
> + }
> +
> + *clock = enable;
> + spin_unlock_irqrestore(&info->clocks_lock, flags);
> +}
> +
> +static void hci_h4p_lazy_clock_release(unsigned long data)
> +{
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&info->lock, flags);
> + if (!info->tx_enabled)
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +/* Power management functions */
> +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable)
> +{
> + u8 v;
> +
> + v = hci_h4p_inb(info, UART_OMAP_SYSC);
> + v &= ~(UART_OMAP_SYSC_IDLEMASK);
> +
> + if (enable)
> + v |= UART_OMAP_SYSC_SMART_IDLE;
> + else
> + v |= UART_OMAP_SYSC_NO_IDLE;
> +
> + hci_h4p_outb(info, UART_OMAP_SYSC, v);
> +}
> +
> +static inline void h4p_schedule_pm(struct hci_h4p_info *info)
> +{
> +}
> +
> +static void hci_h4p_disable_tx(struct hci_h4p_info *info)
> +{
> + if (!info->pm_enabled)
> + return;
> +
> + /* Re-enable smart-idle */
> + hci_h4p_smart_idle(info, 1);
> +
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + mod_timer(&info->lazy_release, jiffies + msecs_to_jiffies(100));
> + info->tx_enabled = 0;
> +}
> +
> +void hci_h4p_enable_tx(struct hci_h4p_info *info)
> +{
> + unsigned long flags;
> +
> + if (!info->pm_enabled)
> + return;
> +
> + h4p_schedule_pm(info);
> +
> + spin_lock_irqsave(&info->lock, flags);
> + del_timer(&info->lazy_release);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + info->tx_enabled = 1;
> + gpio_set_value(info->bt_wakeup_gpio, 1);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + /*
> + * Disable smart-idle as UART TX interrupts
> + * are not wake-up capable
> + */
> + hci_h4p_smart_idle(info, 0);
> +
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +static void hci_h4p_disable_rx(struct hci_h4p_info *info)
> +{
> + if (!info->pm_enabled)
> + return;
> +
> + info->rx_enabled = 0;
> +
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR)
> + return;
> +
> + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT))
> + return;
> +
> + __hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + info->autorts = 0;
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> +}
> +
> +static void hci_h4p_enable_rx(struct hci_h4p_info *info)
> +{
> + if (!info->pm_enabled)
> + return;
> +
> + h4p_schedule_pm(info);
> +
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 1);
> + info->rx_enabled = 1;
> +
> + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT))
> + return;
> +
> + __hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> + info->autorts = 1;
> +}
> +
> +/* Negotiation functions */
> +int hci_h4p_send_alive_packet(struct hci_h4p_info *info)
> +{
> + struct hci_h4p_alive_hdr *hdr;
> + struct hci_h4p_alive_pkt *pkt;
> + struct sk_buff *skb;
> + unsigned long flags;
> + int len;
> +
> + BT_DBG("Sending alive packet");
> +
> + len = H4_TYPE_SIZE + sizeof(*hdr) + sizeof(*pkt);
> + skb = bt_skb_alloc(len, GFP_KERNEL);
> + if (!skb)
> + return -ENOMEM;
> +
> + memset(skb->data, 0x00, len);
> + *skb_put(skb, 1) = H4_ALIVE_PKT;
> + hdr = (struct hci_h4p_alive_hdr *)skb_put(skb, sizeof(*hdr));
> + hdr->dlen = sizeof(*pkt);
> + pkt = (struct hci_h4p_alive_pkt *)skb_put(skb, sizeof(*pkt));
> + pkt->mid = H4P_ALIVE_REQ;
> +
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + BT_DBG("Alive packet sent");
> +
> + return 0;
> +}
> +
> +static void hci_h4p_alive_packet(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + struct hci_h4p_alive_hdr *hdr;
> + struct hci_h4p_alive_pkt *pkt;
> +
> + BT_DBG("Received alive packet");
> + hdr = (struct hci_h4p_alive_hdr *)skb->data;
> + if (hdr->dlen != sizeof(*pkt)) {
> + dev_err(info->dev, "Corrupted alive message\n");
> + info->init_error = -EIO;
> + goto finish_alive;
> + }
> +
> + pkt = (struct hci_h4p_alive_pkt *)skb_pull(skb, sizeof(*hdr));
> + if (pkt->mid != H4P_ALIVE_RESP) {
> + dev_err(info->dev, "Could not negotiate hci_h4p settings\n");
> + info->init_error = -EINVAL;
> + }
> +
> +finish_alive:
> + complete(&info->init_completion);
> + kfree_skb(skb);
> +}
> +
> +static int hci_h4p_send_negotiation(struct hci_h4p_info *info)
> +{
> + struct hci_h4p_neg_cmd *neg_cmd;
> + struct hci_h4p_neg_hdr *neg_hdr;
> + struct sk_buff *skb;
> + unsigned long flags;
> + int err, len;
> + u16 sysclk;
> +
> + BT_DBG("Sending negotiation..");
> +
> + switch (info->bt_sysclk) {
> + case 1:
> + sysclk = 12000;
> + break;
> + case 2:
> + sysclk = 38400;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + len = sizeof(*neg_cmd) + sizeof(*neg_hdr) + H4_TYPE_SIZE;
> + skb = bt_skb_alloc(len, GFP_KERNEL);
> + if (!skb)
> + return -ENOMEM;
> +
> + memset(skb->data, 0x00, len);
> + *skb_put(skb, 1) = H4_NEG_PKT;
> + neg_hdr = (struct hci_h4p_neg_hdr *)skb_put(skb, sizeof(*neg_hdr));
> + neg_cmd = (struct hci_h4p_neg_cmd *)skb_put(skb, sizeof(*neg_cmd));
> +
> + neg_hdr->dlen = sizeof(*neg_cmd);
> + neg_cmd->ack = H4P_NEG_REQ;
> + neg_cmd->baud = cpu_to_le16(BT_BAUDRATE_DIVIDER/MAX_BAUD_RATE);
> + neg_cmd->proto = H4P_PROTO_BYTE;
> + neg_cmd->sys_clk = cpu_to_le16(sysclk);
> +
> + hci_h4p_change_speed(info, INIT_SPEED);
> +
> + hci_h4p_set_rts(info, 1);
> + info->init_error = 0;
> + init_completion(&info->init_completion);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + if (!wait_for_completion_interruptible_timeout(&info->init_completion,
> + msecs_to_jiffies(1000)))
> + return -ETIMEDOUT;
> +
> + if (info->init_error < 0)
> + return info->init_error;
> +
> + /* Change to operational settings */
> + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + hci_h4p_set_rts(info, 0);
> + hci_h4p_change_speed(info, MAX_BAUD_RATE);
> +
> + err = hci_h4p_wait_for_cts(info, 1, 100);
> + if (err < 0)
> + return err;
> +
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> + init_completion(&info->init_completion);
> + err = hci_h4p_send_alive_packet(info);
> +
> + if (err < 0)
> + return err;
> +
> + if (!wait_for_completion_interruptible_timeout(&info->init_completion,
> + msecs_to_jiffies(1000)))
> + return -ETIMEDOUT;
> +
> + if (info->init_error < 0)
> + return info->init_error;
> +
> + BT_DBG("Negotiation successful");
> + return 0;
> +}
> +
> +static void hci_h4p_negotiation_packet(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + struct hci_h4p_neg_hdr *hdr;
> + struct hci_h4p_neg_evt *evt;
> +
> + hdr = (struct hci_h4p_neg_hdr *)skb->data;
> + if (hdr->dlen != sizeof(*evt)) {
> + info->init_error = -EIO;
> + goto finish_neg;
> + }
> +
> + evt = (struct hci_h4p_neg_evt *)skb_pull(skb, sizeof(*hdr));
> +
> + if (evt->ack != H4P_NEG_ACK) {
> + dev_err(info->dev, "Could not negotiate hci_h4p settings\n");
> + info->init_error = -EINVAL;
> + }
> +
> + info->man_id = evt->man_id;
> + info->ver_id = evt->ver_id;
> +
> +finish_neg:
> +
> + complete(&info->init_completion);
> + kfree_skb(skb);
> +}
> +
> +/* H4 packet handling functions */
> +static int hci_h4p_get_hdr_len(struct hci_h4p_info *info, u8 pkt_type)
> +{
> + long retval;
> +
> + switch (pkt_type) {
> + case H4_EVT_PKT:
> + retval = HCI_EVENT_HDR_SIZE;
> + break;
> + case H4_ACL_PKT:
> + retval = HCI_ACL_HDR_SIZE;
> + break;
> + case H4_SCO_PKT:
> + retval = HCI_SCO_HDR_SIZE;
> + break;
> + case H4_NEG_PKT:
> + retval = H4P_NEG_HDR_SIZE;
> + break;
> + case H4_ALIVE_PKT:
> + retval = H4P_ALIVE_HDR_SIZE;
> + break;
> + case H4_RADIO_PKT:
> + retval = H4_RADIO_HDR_SIZE;
> + break;
> + default:
> + dev_err(info->dev, "Unknown H4 packet type 0x%.2x\n", pkt_type);
> + retval = -1;
> + break;
> + }
> +
> + return retval;
> +}
> +
> +static unsigned int hci_h4p_get_data_len(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + long retval = -1;
> + struct hci_acl_hdr *acl_hdr;
> + struct hci_sco_hdr *sco_hdr;
> + struct hci_event_hdr *evt_hdr;
> + struct hci_h4p_neg_hdr *neg_hdr;
> + struct hci_h4p_alive_hdr *alive_hdr;
> + struct hci_h4p_radio_hdr *radio_hdr;
> +
> + switch (bt_cb(skb)->pkt_type) {
> + case H4_EVT_PKT:
> + evt_hdr = (struct hci_event_hdr *)skb->data;
> + retval = evt_hdr->plen;
> + break;
> + case H4_ACL_PKT:
> + acl_hdr = (struct hci_acl_hdr *)skb->data;
> + retval = le16_to_cpu(acl_hdr->dlen);
> + break;
> + case H4_SCO_PKT:
> + sco_hdr = (struct hci_sco_hdr *)skb->data;
> + retval = sco_hdr->dlen;
> + break;
> + case H4_RADIO_PKT:
> + radio_hdr = (struct hci_h4p_radio_hdr *)skb->data;
> + retval = radio_hdr->dlen;
> + break;
> + case H4_NEG_PKT:
> + neg_hdr = (struct hci_h4p_neg_hdr *)skb->data;
> + retval = neg_hdr->dlen;
> + break;
> + case H4_ALIVE_PKT:
> + alive_hdr = (struct hci_h4p_alive_hdr *)skb->data;
> + retval = alive_hdr->dlen;
> + break;
> + }
> +
> + return retval;
> +}
> +
> +static inline void hci_h4p_recv_frame(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + if (unlikely(!test_bit(HCI_RUNNING, &info->hdev->flags))) {
> + switch (bt_cb(skb)->pkt_type) {
> + case H4_NEG_PKT:
> + hci_h4p_negotiation_packet(info, skb);
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + return;
> + case H4_ALIVE_PKT:
> + hci_h4p_alive_packet(info, skb);
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + return;
> + }
> +
> + if (!test_bit(HCI_UP, &info->hdev->flags)) {
> + BT_DBG("fw_event");
> + hci_h4p_parse_fw_event(info, skb);
> + return;
> + }
> + }
> +
> + hci_recv_frame(info->hdev, skb);
> + BT_DBG("Frame sent to upper layer");
> +}
> +
> +static inline void hci_h4p_handle_byte(struct hci_h4p_info *info, u8 byte)
> +{
> + switch (info->rx_state) {
> + case WAIT_FOR_PKT_TYPE:
> + bt_cb(info->rx_skb)->pkt_type = byte;
> + info->rx_count = hci_h4p_get_hdr_len(info, byte);
> + if (info->rx_count < 0) {
> + info->hdev->stat.err_rx++;
> + kfree_skb(info->rx_skb);
> + info->rx_skb = NULL;
> + } else {
> + info->rx_state = WAIT_FOR_HEADER;
> + }
> + break;
> + case WAIT_FOR_HEADER:
> + info->rx_count--;
> + *skb_put(info->rx_skb, 1) = byte;
> + if (info->rx_count != 0)
> + break;
> + info->rx_count = hci_h4p_get_data_len(info, info->rx_skb);
> + if (info->rx_count > skb_tailroom(info->rx_skb)) {
> + dev_err(info->dev, "frame too long\n");
> + info->garbage_bytes = info->rx_count
> + - skb_tailroom(info->rx_skb);
> + kfree_skb(info->rx_skb);
> + info->rx_skb = NULL;
> + break;
> + }
> + info->rx_state = WAIT_FOR_DATA;
> + break;
> + case WAIT_FOR_DATA:
> + info->rx_count--;
> + *skb_put(info->rx_skb, 1) = byte;
> + break;
> + default:
> + WARN_ON(1);
> + break;
> + }
> +
> + if (info->rx_count == 0) {
> + /* H4+ devices should always send word aligned packets */
> + if (!(info->rx_skb->len % 2))
> + info->garbage_bytes++;
> + hci_h4p_recv_frame(info, info->rx_skb);
> + info->rx_skb = NULL;
> + }
> +}
> +
> +static void hci_h4p_rx_tasklet(unsigned long data)
> +{
> + u8 byte;
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> +
> + BT_DBG("tasklet woke up");
> + BT_DBG("rx_tasklet woke up");
> +
> + while (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR) {
> + byte = hci_h4p_inb(info, UART_RX);
> + if (info->garbage_bytes) {
> + info->garbage_bytes--;
> + continue;
> + }
> + if (info->rx_skb == NULL) {
> + info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE,
> + GFP_ATOMIC | GFP_DMA);
> + if (!info->rx_skb) {
> + dev_err(info->dev,
> + "No memory for new packet\n");
> + goto finish_rx;
> + }
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + info->rx_skb->dev = (void *)info->hdev;
> + }
> + info->hdev->stat.byte_rx++;
> + hci_h4p_handle_byte(info, byte);
> + }
> +
> + if (!info->rx_enabled) {
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT &&
> + info->autorts) {
> + __hci_h4p_set_auto_ctsrts(info, 0 , UART_EFR_RTS);
> + info->autorts = 0;
> + }
> + /* Flush posted write to avoid spurious interrupts */
> + hci_h4p_inb(info, UART_OMAP_SCR);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> + }
> +
> +finish_rx:
> + BT_DBG("rx_ended");
> +}
> +
> +static void hci_h4p_tx_tasklet(unsigned long data)
> +{
> + unsigned int sent = 0;
> + struct sk_buff *skb;
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> +
> + BT_DBG("tasklet woke up");
> + BT_DBG("tx_tasklet woke up");
> +
> + if (info->autorts != info->rx_enabled) {
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) {
> + if (info->autorts && !info->rx_enabled) {
> + __hci_h4p_set_auto_ctsrts(info, 0,
> + UART_EFR_RTS);
> + info->autorts = 0;
> + }
> + if (!info->autorts && info->rx_enabled) {
> + __hci_h4p_set_auto_ctsrts(info, 1,
> + UART_EFR_RTS);
> + info->autorts = 1;
> + }
> + } else {
> + hci_h4p_outb(info, UART_OMAP_SCR,
> + hci_h4p_inb(info, UART_OMAP_SCR) |
> + UART_OMAP_SCR_EMPTY_THR);
> + goto finish_tx;
> + }
> + }
> +
> + skb = skb_dequeue(&info->txq);
> + if (!skb) {
> + /* No data in buffer */
> + BT_DBG("skb ready");
> + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) {
> + hci_h4p_outb(info, UART_IER,
> + hci_h4p_inb(info, UART_IER) &
> + ~UART_IER_THRI);
> + hci_h4p_inb(info, UART_OMAP_SCR);
> + hci_h4p_disable_tx(info);
> + return;
> + }
> + hci_h4p_outb(info, UART_OMAP_SCR,
> + hci_h4p_inb(info, UART_OMAP_SCR) |
> + UART_OMAP_SCR_EMPTY_THR);
> + goto finish_tx;
> + }
> +
> + /* Copy data to tx fifo */
> + while (!(hci_h4p_inb(info, UART_OMAP_SSR) & UART_OMAP_SSR_TXFULL) &&
> + (sent < skb->len)) {
> + hci_h4p_outb(info, UART_TX, skb->data[sent]);
> + sent++;
> + }
> +
> + info->hdev->stat.byte_tx += sent;
> + if (skb->len == sent) {
> + kfree_skb(skb);
> + } else {
> + skb_pull(skb, sent);
> + skb_queue_head(&info->txq, skb);
> + }
> +
> + hci_h4p_outb(info, UART_OMAP_SCR, hci_h4p_inb(info, UART_OMAP_SCR) &
> + ~UART_OMAP_SCR_EMPTY_THR);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> +
> +finish_tx:
> + /* Flush posted write to avoid spurious interrupts */
> + hci_h4p_inb(info, UART_OMAP_SCR);
> +
> +}
> +
> +static irqreturn_t hci_h4p_interrupt(int irq, void *data)
> +{
> + struct hci_h4p_info *info = (struct hci_h4p_info *)data;
> + u8 iir, msr;
> + int ret;
> +
> + ret = IRQ_NONE;
> +
> + iir = hci_h4p_inb(info, UART_IIR);
> + if (iir & UART_IIR_NO_INT)
> + return IRQ_HANDLED;
> +
> + BT_DBG("In interrupt handler iir 0x%.2x", iir);
> +
> + iir &= UART_IIR_ID;
> +
> + if (iir == UART_IIR_MSI) {
> + msr = hci_h4p_inb(info, UART_MSR);
> + ret = IRQ_HANDLED;
> + }
> + if (iir == UART_IIR_RLSI) {
> + hci_h4p_inb(info, UART_RX);
> + hci_h4p_inb(info, UART_LSR);
> + ret = IRQ_HANDLED;
> + }
> +
> + if (iir == UART_IIR_RDI) {
> + hci_h4p_rx_tasklet((unsigned long)data);
> + ret = IRQ_HANDLED;
> + }
> +
> + if (iir == UART_IIR_THRI) {
> + hci_h4p_tx_tasklet((unsigned long)data);
> + ret = IRQ_HANDLED;
> + }
> +
> + return ret;
> +}
> +
> +static irqreturn_t hci_h4p_wakeup_interrupt(int irq, void *dev_inst)
> +{
> + struct hci_h4p_info *info = dev_inst;
> + int should_wakeup;
> + struct hci_dev *hdev;
> +
> + if (!info->hdev)
> + return IRQ_HANDLED;
> +
> + should_wakeup = gpio_get_value(info->host_wakeup_gpio);
> + hdev = info->hdev;
> +
> + if (!test_bit(HCI_RUNNING, &hdev->flags)) {
> + if (should_wakeup == 1)
> + complete_all(&info->test_completion);
> +
> + return IRQ_HANDLED;
> + }
> +
> + BT_DBG("gpio interrupt %d", should_wakeup);
> +
> + /* Check if wee have missed some interrupts */
> + if (info->rx_enabled == should_wakeup)
> + return IRQ_HANDLED;
> +
> + if (should_wakeup)
> + hci_h4p_enable_rx(info);
> + else
> + hci_h4p_disable_rx(info);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static inline void hci_h4p_set_pm_limits(struct hci_h4p_info *info, bool set)
> +{
> + struct hci_h4p_platform_data *bt_plat_data = info->dev->platform_data;
> + const char *sset = set ? "set" : "clear";
> +
> + if (unlikely(!bt_plat_data || !bt_plat_data->set_pm_limits))
> + return;
> +
> + if (set != !!test_bit(H4P_ACTIVE_MODE, &info->pm_flags)) {
> + bt_plat_data->set_pm_limits(info->dev, set);
> + if (set)
> + set_bit(H4P_ACTIVE_MODE, &info->pm_flags);
> + else
> + clear_bit(H4P_ACTIVE_MODE, &info->pm_flags);
> + BT_DBG("Change pm constraints to: %s", sset);
> + return;
> + }
> +
> + BT_DBG("pm constraints remains: %s", sset);
> +}
> +
> +static int hci_h4p_reset(struct hci_h4p_info *info)
> +{
> + int err;
> +
> + err = hci_h4p_reset_uart(info);
> + if (err < 0) {
> + dev_err(info->dev, "Uart reset failed\n");
> + return err;
> + }
> + hci_h4p_init_uart(info);
> + hci_h4p_set_rts(info, 0);
> +
> + gpio_set_value(info->reset_gpio, 0);
> + gpio_set_value(info->bt_wakeup_gpio, 1);
> + msleep(10);
> +
> + if (gpio_get_value(info->host_wakeup_gpio) == 1) {
> + dev_err(info->dev, "host_wakeup_gpio not low\n");
> + return -EPROTO;
> + }
> +
> + init_completion(&info->test_completion);
> + gpio_set_value(info->reset_gpio, 1);
> +
> + if (!wait_for_completion_interruptible_timeout(&info->test_completion,
> + msecs_to_jiffies(100))) {
> + dev_err(info->dev, "wakeup test timed out\n");
> + complete_all(&info->test_completion);
> + return -EPROTO;
> + }
> +
> + err = hci_h4p_wait_for_cts(info, 1, 100);
> + if (err < 0) {
> + dev_err(info->dev, "No cts from bt chip\n");
> + return err;
> + }
> +
> + hci_h4p_set_rts(info, 1);
> +
> + return 0;
> +}
> +
> +/* hci callback functions */
> +static int hci_h4p_hci_flush(struct hci_dev *hdev)
> +{
> + struct hci_h4p_info *info = hci_get_drvdata(hdev);
> + skb_queue_purge(&info->txq);
> +
> + return 0;
> +}
> +
> +static int hci_h4p_bt_wakeup_test(struct hci_h4p_info *info)
> +{
> + /*
> + * Test Sequence:
> + * Host de-asserts the BT_WAKE_UP line.
> + * Host polls the UART_CTS line, waiting for it to be de-asserted.
> + * Host asserts the BT_WAKE_UP line.
> + * Host polls the UART_CTS line, waiting for it to be asserted.
> + * Host de-asserts the BT_WAKE_UP line (allow the Bluetooth device to
> + * sleep).
> + * Host polls the UART_CTS line, waiting for it to be de-asserted.
> + */
> + int err;
> + int ret = -ECOMM;
> +
> + if (!info)
> + return -EINVAL;
> +
> + /* Disable wakeup interrupts */
> + disable_irq(gpio_to_irq(info->host_wakeup_gpio));
> +
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + err = hci_h4p_wait_for_cts(info, 0, 100);
> + if (err) {
> + dev_warn(info->dev, "bt_wakeup_test: fail: "
> + "CTS low timed out: %d\n", err);
> + goto out;
> + }
> +
> + gpio_set_value(info->bt_wakeup_gpio, 1);
> + err = hci_h4p_wait_for_cts(info, 1, 100);
> + if (err) {
> + dev_warn(info->dev, "bt_wakeup_test: fail: "
> + "CTS high timed out: %d\n", err);
> + goto out;
> + }
> +
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + err = hci_h4p_wait_for_cts(info, 0, 100);
> + if (err) {
> + dev_warn(info->dev, "bt_wakeup_test: fail: "
> + "CTS re-low timed out: %d\n", err);
> + goto out;
> + }
> +
> + ret = 0;
> +
> +out:
> +
> + /* Re-enable wakeup interrupts */
> + enable_irq(gpio_to_irq(info->host_wakeup_gpio));
> +
> + return ret;
> +}
> +
> +static int hci_h4p_hci_open(struct hci_dev *hdev)
> +{
> + struct hci_h4p_info *info;
> + int err, retries = 0;
> + struct sk_buff_head fw_queue;
> + unsigned long flags;
> +
> + info = hci_get_drvdata(hdev);
> +
> + if (test_bit(HCI_RUNNING, &hdev->flags))
> + return 0;
> +
> + /* TI1271 has HW bug and boot up might fail. Retry up to three times */
> +again:
> +
> + info->rx_enabled = 1;
> + info->rx_state = WAIT_FOR_PKT_TYPE;
> + info->rx_count = 0;
> + info->garbage_bytes = 0;
> + info->rx_skb = NULL;
> + info->pm_enabled = 0;
> + init_completion(&info->fw_completion);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 1);
> + skb_queue_head_init(&fw_queue);
> +
> + err = hci_h4p_reset(info);
> + if (err < 0)
> + goto err_clean;
> +
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_CTS | UART_EFR_RTS);
> + info->autorts = 1;
> +
> + err = hci_h4p_send_negotiation(info);
> +
> + err = hci_h4p_read_fw(info, &fw_queue);
> + if (err < 0) {
> + dev_err(info->dev, "Cannot read firmware\n");
> + goto err_clean;
> + }
> +
> + err = hci_h4p_send_fw(info, &fw_queue);
> + if (err < 0) {
> + dev_err(info->dev, "Sending firmware failed.\n");
> + goto err_clean;
> + }
> +
> + info->pm_enabled = 1;
> +
> + err = hci_h4p_bt_wakeup_test(info);
> + if (err < 0) {
> + dev_err(info->dev, "BT wakeup test failed.\n");
> + goto err_clean;
> + }
> +
> + spin_lock_irqsave(&info->lock, flags);
> + info->rx_enabled = gpio_get_value(info->host_wakeup_gpio);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, info->rx_enabled);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> +
> + kfree_skb(info->alive_cmd_skb);
> + info->alive_cmd_skb = NULL;
> + set_bit(HCI_RUNNING, &hdev->flags);
> +
> + BT_DBG("hci up and running");
> + return 0;
> +
> +err_clean:
> + hci_h4p_hci_flush(hdev);
> + hci_h4p_reset_uart(info);
> + del_timer_sync(&info->lazy_release);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> + gpio_set_value(info->reset_gpio, 0);
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + skb_queue_purge(&fw_queue);
> + kfree_skb(info->alive_cmd_skb);
> + info->alive_cmd_skb = NULL;
> + kfree_skb(info->rx_skb);
> + info->rx_skb = NULL;
> +
> + if (retries++ < 3) {
> + dev_err(info->dev, "FW loading try %d fail. Retry.\n", retries);
> + goto again;
> + }
> +
> + return err;
> +}
> +
> +static int hci_h4p_hci_close(struct hci_dev *hdev)
> +{
> + struct hci_h4p_info *info = hci_get_drvdata(hdev);
> +
> + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
> + return 0;
> +
> + hci_h4p_hci_flush(hdev);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 1);
> + hci_h4p_reset_uart(info);
> + del_timer_sync(&info->lazy_release);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> + hci_h4p_set_clk(info, &info->rx_clocks_en, 0);
> + gpio_set_value(info->reset_gpio, 0);
> + gpio_set_value(info->bt_wakeup_gpio, 0);
> + kfree_skb(info->rx_skb);
> +
> + return 0;
> +}
> +
> +static int hci_h4p_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct hci_h4p_info *info;
> + int err = 0;
> +
> + BT_DBG("dev %p, skb %p", hdev, skb);
> +
> + info = hci_get_drvdata(hdev);
> +
> + if (!test_bit(HCI_RUNNING, &hdev->flags)) {
> + dev_warn(info->dev, "Frame for non-running device\n");
> + return -EIO;
> + }
> +
> + switch (bt_cb(skb)->pkt_type) {
> + case HCI_COMMAND_PKT:
> + hdev->stat.cmd_tx++;
> + break;
> + case HCI_ACLDATA_PKT:
> + hdev->stat.acl_tx++;
> + break;
> + case HCI_SCODATA_PKT:
> + hdev->stat.sco_tx++;
> + break;
> + }
> +
> + /* Push frame type to skb */
> + *skb_push(skb, 1) = (bt_cb(skb)->pkt_type);
> + /* We should allways send word aligned data to h4+ devices */
> + if (skb->len % 2) {
> + err = skb_pad(skb, 1);
> + if (!err)
> + *skb_put(skb, 1) = 0x00;
> + }
> + if (err)
> + return err;
> +
> + skb_queue_tail(&info->txq, skb);
> + hci_h4p_enable_tx(info);
> +
> + return 0;
> +}
> +
> +static ssize_t hci_h4p_store_bdaddr(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct hci_h4p_info *info = dev_get_drvdata(dev);
> + unsigned int bdaddr[6];
> + int ret, i;
> +
> + ret = sscanf(buf, "%2x:%2x:%2x:%2x:%2x:%2x\n",
> + &bdaddr[0], &bdaddr[1], &bdaddr[2],
> + &bdaddr[3], &bdaddr[4], &bdaddr[5]);
> +
> + if (ret != 6)
> + return -EINVAL;
> +
> + for (i = 0; i < 6; i++) {
> + if (bdaddr[i] > 0xff)
> + return -EINVAL;
> + info->bd_addr[i] = bdaddr[i] & 0xff;
> + }
> +
> + return count;
> +}
> +
> +static ssize_t hci_h4p_show_bdaddr(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct hci_h4p_info *info = dev_get_drvdata(dev);
> +
> + return sprintf(buf, "%pMR\n", info->bd_addr);
> +}
> +
> +static DEVICE_ATTR(bdaddr, S_IRUGO | S_IWUSR, hci_h4p_show_bdaddr,
> + hci_h4p_store_bdaddr);
> +
> +static int hci_h4p_sysfs_create_files(struct device *dev)
> +{
> + return device_create_file(dev, &dev_attr_bdaddr);
> +}
> +
> +static void hci_h4p_sysfs_remove_files(struct device *dev)
> +{
> + device_remove_file(dev, &dev_attr_bdaddr);
> +}
> +
> +static int hci_h4p_register_hdev(struct hci_h4p_info *info)
> +{
> + struct hci_dev *hdev;
> +
> + /* Initialize and register HCI device */
> +
> + hdev = hci_alloc_dev();
> + if (!hdev) {
> + dev_err(info->dev, "Can't allocate memory for device\n");
> + return -ENOMEM;
> + }
> + info->hdev = hdev;
> +
> + hdev->bus = HCI_UART;
> + hci_set_drvdata(hdev, info);
> +
> + hdev->open = hci_h4p_hci_open;
> + hdev->close = hci_h4p_hci_close;
> + hdev->flush = hci_h4p_hci_flush;
> + hdev->send = hci_h4p_hci_send_frame;
> + set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);
> +
> + SET_HCIDEV_DEV(hdev, info->dev);
> +
> + if (hci_h4p_sysfs_create_files(info->dev) < 0) {
> + dev_err(info->dev, "failed to create sysfs files\n");
> + goto free;
> + }
> +
> + if (hci_register_dev(hdev) >= 0)
> + return 0;
> +
> + dev_err(info->dev, "hci_register failed %s.\n", hdev->name);
> + hci_h4p_sysfs_remove_files(info->dev);
> +free:
> + hci_free_dev(info->hdev);
> + return -ENODEV;
> +}
> +
> +static int hci_h4p_probe(struct platform_device *pdev)
> +{
> + struct hci_h4p_platform_data *bt_plat_data;
> + struct hci_h4p_info *info;
> + int err;
> +
> + dev_info(&pdev->dev, "Registering HCI H4P device\n");
> + info = devm_kzalloc(&pdev->dev, sizeof(struct hci_h4p_info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + info->dev = &pdev->dev;
> + info->tx_enabled = 1;
> + info->rx_enabled = 1;
> + spin_lock_init(&info->lock);
> + spin_lock_init(&info->clocks_lock);
> + skb_queue_head_init(&info->txq);
> +
> + if (pdev->dev.platform_data == NULL) {
> + dev_err(&pdev->dev, "Could not get Bluetooth config data\n");
> + return -ENODATA;
> + }
> +
> + bt_plat_data = pdev->dev.platform_data;
> + info->chip_type = bt_plat_data->chip_type;
> + info->bt_wakeup_gpio = bt_plat_data->bt_wakeup_gpio;
> + info->host_wakeup_gpio = bt_plat_data->host_wakeup_gpio;
> + info->reset_gpio = bt_plat_data->reset_gpio;
> + info->reset_gpio_shared = bt_plat_data->reset_gpio_shared;
> + info->bt_sysclk = bt_plat_data->bt_sysclk;
> +
> + BT_DBG("RESET gpio: %d", info->reset_gpio);
> + BT_DBG("BTWU gpio: %d", info->bt_wakeup_gpio);
> + BT_DBG("HOSTWU gpio: %d", info->host_wakeup_gpio);
> + BT_DBG("sysclk: %d", info->bt_sysclk);
> +
> + init_completion(&info->test_completion);
> + complete_all(&info->test_completion);
> +
> + if (!info->reset_gpio_shared) {
> + err = devm_gpio_request_one(&pdev->dev, info->reset_gpio,
> + GPIOF_OUT_INIT_LOW, "bt_reset");
> + if (err < 0) {
> + dev_err(&pdev->dev, "Cannot get GPIO line %d\n",
> + info->reset_gpio);
> + return err;
> + }
> + }
> +
> + err = devm_gpio_request_one(&pdev->dev, info->bt_wakeup_gpio,
> + GPIOF_OUT_INIT_LOW, "bt_wakeup");
> +
> + if (err < 0) {
> + dev_err(info->dev, "Cannot get GPIO line 0x%d",
> + info->bt_wakeup_gpio);
> + return err;
> + }
> +
> + err = devm_gpio_request_one(&pdev->dev, info->host_wakeup_gpio,
> + GPIOF_DIR_IN, "host_wakeup");
> + if (err < 0) {
> + dev_err(info->dev, "Cannot get GPIO line %d",
> + info->host_wakeup_gpio);
> + return err;
> + }
> +
> + info->irq = bt_plat_data->uart_irq;
> + info->uart_base = devm_ioremap(&pdev->dev, bt_plat_data->uart_base, SZ_2K);
> + info->uart_iclk = devm_clk_get(&pdev->dev, bt_plat_data->uart_iclk);
> + info->uart_fclk = devm_clk_get(&pdev->dev, bt_plat_data->uart_fclk);
> +
> + err = devm_request_irq(&pdev->dev, info->irq, hci_h4p_interrupt, IRQF_DISABLED,
> + "hci_h4p", info);
> + if (err < 0) {
> + dev_err(info->dev, "hci_h4p: unable to get IRQ %d\n", info->irq);
> + return err;
> + }
> +
> + err = devm_request_irq(&pdev->dev, gpio_to_irq(info->host_wakeup_gpio),
> + hci_h4p_wakeup_interrupt, IRQF_TRIGGER_FALLING |
> + IRQF_TRIGGER_RISING | IRQF_DISABLED,
> + "hci_h4p_wkup", info);
> + if (err < 0) {
> + dev_err(info->dev, "hci_h4p: unable to get wakeup IRQ %d\n",
> + gpio_to_irq(info->host_wakeup_gpio));
> + return err;
> + }
> +
> + err = irq_set_irq_wake(gpio_to_irq(info->host_wakeup_gpio), 1);
> + if (err < 0) {
> + dev_err(info->dev, "hci_h4p: unable to set wakeup for IRQ %d\n",
> + gpio_to_irq(info->host_wakeup_gpio));
> + return err;
> + }
> +
> + init_timer_deferrable(&info->lazy_release);
> + info->lazy_release.function = hci_h4p_lazy_clock_release;
> + info->lazy_release.data = (unsigned long)info;
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 1);
> + err = hci_h4p_reset_uart(info);
> + if (err < 0)
> + return err;
> + gpio_set_value(info->reset_gpio, 0);
> + hci_h4p_set_clk(info, &info->tx_clocks_en, 0);
> +
> + platform_set_drvdata(pdev, info);
> +
> + if (hci_h4p_register_hdev(info) < 0) {
> + dev_err(info->dev, "failed to register hci_h4p hci device\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int hci_h4p_remove(struct platform_device *pdev)
> +{
> + struct hci_h4p_info *info;
> +
> + info = platform_get_drvdata(pdev);
> +
> + hci_h4p_sysfs_remove_files(info->dev);
> + hci_h4p_hci_close(info->hdev);
> + hci_unregister_dev(info->hdev);
> + hci_free_dev(info->hdev);
> +
> + return 0;
> +}
> +
> +static struct platform_driver hci_h4p_driver = {
> + .probe = hci_h4p_probe,
> + .remove = hci_h4p_remove,
> + .driver = {
> + .name = "hci_h4p",
> + },
> +};
> +
> +module_platform_driver(hci_h4p_driver);
> +
> +MODULE_ALIAS("platform:hci_h4p");
> +MODULE_DESCRIPTION("Bluetooth h4 driver with nokia extensions");
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Ville Tervo");
> +MODULE_FIRMWARE(FW_NAME_TI1271_PRELE);
> +MODULE_FIRMWARE(FW_NAME_TI1271_LE);
> +MODULE_FIRMWARE(FW_NAME_TI1271);
> +MODULE_FIRMWARE(FW_NAME_BCM2048);
> +MODULE_FIRMWARE(FW_NAME_CSR);
> diff --git a/drivers/bluetooth/nokia_fw-bcm.c b/drivers/bluetooth/nokia_fw-bcm.c
> new file mode 100644
> index 0000000..e8912bf
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw-bcm.c
> @@ -0,0 +1,147 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/delay.h>
> +#include <linux/serial_reg.h>
> +
> +#include "hci_h4p.h"
> +
> +static int hci_h4p_bcm_set_bdaddr(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + int i;
> + static const u8 nokia_oui[3] = {0x00, 0x1f, 0xdf};
> + int not_valid;
> +
> + not_valid = 1;
> + for (i = 0; i < 6; i++) {
> + if (info->bd_addr[i] != 0x00) {
> + not_valid = 0;
> + break;
> + }
> + }
> +
> + if (not_valid) {
> + dev_info(info->dev, "Valid bluetooth address not found, setting some random\n");
> + /* When address is not valid, use some random but Nokia MAC */
> + memcpy(info->bd_addr, nokia_oui, 3);
> + get_random_bytes(info->bd_addr + 3, 3);
> + }
> +
> + for (i = 0; i < 6; i++)
> + skb->data[9 - i] = info->bd_addr[i];
> +
> + return 0;
> +}
> +
> +void hci_h4p_bcm_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + struct sk_buff *fw_skb;
> + int err;
> + unsigned long flags;
> +
> + if (skb->data[5] != 0x00) {
> + dev_err(info->dev, "Firmware sending command failed 0x%.2x\n",
> + skb->data[5]);
> + info->fw_error = -EPROTO;
> + }
> +
> + kfree_skb(skb);
> +
> + fw_skb = skb_dequeue(info->fw_q);
> + if (fw_skb == NULL || info->fw_error) {
> + complete(&info->fw_completion);
> + return;
> + }
> +
> + if (fw_skb->data[1] == 0x01 && fw_skb->data[2] == 0xfc && fw_skb->len >= 10) {
> + BT_DBG("Setting bluetooth address");
> + err = hci_h4p_bcm_set_bdaddr(info, fw_skb);
> + if (err < 0) {
> + kfree_skb(fw_skb);
> + info->fw_error = err;
> + complete(&info->fw_completion);
> + return;
> + }
> + }
> +
> + skb_queue_tail(&info->txq, fw_skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +
> +int hci_h4p_bcm_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue)
> +{
> + struct sk_buff *skb;
> + unsigned long flags, time;
> +
> + info->fw_error = 0;
> +
> + BT_DBG("Sending firmware");
> +
> + time = jiffies;
> +
> + info->fw_q = fw_queue;
> + skb = skb_dequeue(fw_queue);
> + if (!skb)
> + return -ENODATA;
> +
> + BT_DBG("Sending commands");
> +
> + /*
> + * Disable smart-idle as UART TX interrupts
> + * are not wake-up capable
> + */
> + hci_h4p_smart_idle(info, 0);
> +
> + /* Check if this is bd_address packet */
> + init_completion(&info->fw_completion);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + if (!wait_for_completion_timeout(&info->fw_completion,
> + msecs_to_jiffies(2000))) {
> + dev_err(info->dev, "No reply to fw command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->fw_error) {
> + dev_err(info->dev, "FW error\n");
> + return -EPROTO;
> + }
> +
> + BT_DBG("Firmware sent in %d msecs",
> + jiffies_to_msecs(jiffies-time));
> +
> + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + hci_h4p_set_rts(info, 0);
> + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE);
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> +
> + return 0;
> +}
> diff --git a/drivers/bluetooth/nokia_fw-csr.c b/drivers/bluetooth/nokia_fw-csr.c
> new file mode 100644
> index 0000000..e39c4a3
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw-csr.c
> @@ -0,0 +1,150 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005-2008 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/delay.h>
> +#include <linux/serial_reg.h>
> +
> +#include "hci_h4p.h"
> +
> +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + /* Check if this is fw packet */
> + if (skb->data[0] != 0xff) {
> + hci_recv_frame(info->hdev, skb);
> + return;
> + }
> +
> + if (skb->data[11] || skb->data[12]) {
> + dev_err(info->dev, "Firmware sending command failed\n");
> + info->fw_error = -EPROTO;
> + }
> +
> + kfree_skb(skb);
> + complete(&info->fw_completion);
> +}
> +
> +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue)
> +{
> + static const u8 nokia_oui[3] = {0x00, 0x19, 0x4F};
> + struct sk_buff *skb;
> + unsigned int offset;
> + int retries, count, i, not_valid;
> + unsigned long flags;
> +
> + info->fw_error = 0;
> +
> + BT_DBG("Sending firmware");
> + skb = skb_dequeue(fw_queue);
> +
> + if (!skb)
> + return -ENOMSG;
> +
> + /* Check if this is bd_address packet */
> + if (skb->data[15] == 0x01 && skb->data[16] == 0x00) {
> + offset = 21;
> + skb->data[offset + 1] = 0x00;
> + skb->data[offset + 5] = 0x00;
> +
> + not_valid = 1;
> + for (i = 0; i < 6; i++) {
> + if (info->bd_addr[i] != 0x00) {
> + not_valid = 0;
> + break;
> + }
> + }
> +
> + if (not_valid) {
> + dev_info(info->dev, "Valid bluetooth address not found,"
> + " setting some random\n");
> + /* When address is not valid, use some random */
> + memcpy(info->bd_addr, nokia_oui, 3);
> + get_random_bytes(info->bd_addr + 3, 3);
> + }
> +
> + skb->data[offset + 7] = info->bd_addr[0];
> + skb->data[offset + 6] = info->bd_addr[1];
> + skb->data[offset + 4] = info->bd_addr[2];
> + skb->data[offset + 0] = info->bd_addr[3];
> + skb->data[offset + 3] = info->bd_addr[4];
> + skb->data[offset + 2] = info->bd_addr[5];
> + }
> +
> + for (count = 1; ; count++) {
> + BT_DBG("Sending firmware command %d", count);
> + init_completion(&info->fw_completion);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + skb = skb_dequeue(fw_queue);
> + if (!skb)
> + break;
> +
> + if (!wait_for_completion_timeout(&info->fw_completion,
> + msecs_to_jiffies(1000))) {
> + dev_err(info->dev, "No reply to fw command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->fw_error) {
> + dev_err(info->dev, "FW error\n");
> + return -EPROTO;
> + }
> + };
> +
> + /* Wait for chip warm reset */
> + retries = 100;
> + while ((!skb_queue_empty(&info->txq) ||
> + !(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT)) &&
> + retries--) {
> + msleep(10);
> + }
> + if (!retries) {
> + dev_err(info->dev, "Transmitter not empty\n");
> + return -ETIMEDOUT;
> + }
> +
> + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE);
> +
> + if (hci_h4p_wait_for_cts(info, 1, 100)) {
> + dev_err(info->dev, "cts didn't deassert after final speed\n");
> + return -ETIMEDOUT;
> + }
> +
> + retries = 100;
> + do {
> + init_completion(&info->init_completion);
> + hci_h4p_send_alive_packet(info);
> + retries--;
> + } while (!wait_for_completion_timeout(&info->init_completion, 100) &&
> + retries > 0);
> +
> + if (!retries) {
> + dev_err(info->dev, "No alive reply after speed change\n");
> + return -ETIMEDOUT;
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/bluetooth/nokia_fw-ti1273.c b/drivers/bluetooth/nokia_fw-ti1273.c
> new file mode 100644
> index 0000000..f5500f7
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw-ti1273.c
> @@ -0,0 +1,110 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2009 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/delay.h>
> +#include <linux/serial_reg.h>
> +
> +#include "hci_h4p.h"
> +
> +static struct sk_buff_head *fw_q;
> +
> +void hci_h4p_ti1273_parse_fw_event(struct hci_h4p_info *info,
> + struct sk_buff *skb)
> +{
> + struct sk_buff *fw_skb;
> + unsigned long flags;
> +
> + if (skb->data[5] != 0x00) {
> + dev_err(info->dev, "Firmware sending command failed 0x%.2x\n",
> + skb->data[5]);
> + info->fw_error = -EPROTO;
> + }
> +
> + kfree_skb(skb);
> +
> + fw_skb = skb_dequeue(fw_q);
> + if (fw_skb == NULL || info->fw_error) {
> + complete(&info->fw_completion);
> + return;
> + }
> +
> + skb_queue_tail(&info->txq, fw_skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +
> +int hci_h4p_ti1273_send_fw(struct hci_h4p_info *info,
> + struct sk_buff_head *fw_queue)
> +{
> + struct sk_buff *skb;
> + unsigned long flags, time;
> +
> + info->fw_error = 0;
> +
> + BT_DBG("Sending firmware");
> +
> + time = jiffies;
> +
> + fw_q = fw_queue;
> + skb = skb_dequeue(fw_queue);
> + if (!skb)
> + return -ENODATA;
> +
> + BT_DBG("Sending commands");
> + /* Check if this is bd_address packet */
> + init_completion(&info->fw_completion);
> + hci_h4p_smart_idle(info, 0);
> + skb_queue_tail(&info->txq, skb);
> + spin_lock_irqsave(&info->lock, flags);
> + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) |
> + UART_IER_THRI);
> + spin_unlock_irqrestore(&info->lock, flags);
> +
> + if (!wait_for_completion_timeout(&info->fw_completion,
> + msecs_to_jiffies(2000))) {
> + dev_err(info->dev, "No reply to fw command\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->fw_error) {
> + dev_err(info->dev, "FW error\n");
> + return -EPROTO;
> + }
> +
> + BT_DBG("Firmware sent in %d msecs",
> + jiffies_to_msecs(jiffies-time));
> +
> + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS);
> + hci_h4p_set_rts(info, 0);
> + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE);
> + if (hci_h4p_wait_for_cts(info, 1, 100)) {
> + dev_err(info->dev,
> + "cts didn't go down after final speed change\n");
> + return -ETIMEDOUT;
> + }
> + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS);
> +
> + return 0;
> +}
> diff --git a/drivers/bluetooth/nokia_fw.c b/drivers/bluetooth/nokia_fw.c
> new file mode 100644
> index 0000000..cfea61c
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_fw.c
> @@ -0,0 +1,195 @@
> +/*
> + * This file is part of hci_h4p bluetooth driver
> + *
> + * Copyright (C) 2005, 2006 Nokia Corporation.
> + *
> + * Contact: Ville Tervo <ville.tervo@nokia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/firmware.h>
> +#include <linux/clk.h>
> +
> +#include <net/bluetooth/bluetooth.h>
> +
> +#include "hci_h4p.h"
> +
> +static int fw_pos;
> +
> +/* Firmware handling */
> +static int hci_h4p_open_firmware(struct hci_h4p_info *info,
> + const struct firmware **fw_entry)
> +{
> + int err;
> +
> + fw_pos = 0;
> + BT_DBG("Opening firmware man_id 0x%.2x ver_id 0x%.2x",
> + info->man_id, info->ver_id);
> + switch (info->man_id) {
> + case H4P_ID_TI1271:
> + switch (info->ver_id) {
> + case 0xe1:
> + err = request_firmware(fw_entry, FW_NAME_TI1271_PRELE,
> + info->dev);
> + break;
> + case 0xd1:
> + case 0xf1:
> + err = request_firmware(fw_entry, FW_NAME_TI1271_LE,
> + info->dev);
> + break;
> + default:
> + err = request_firmware(fw_entry, FW_NAME_TI1271,
> + info->dev);
> + }
> + break;
> + case H4P_ID_CSR:
> + err = request_firmware(fw_entry, FW_NAME_CSR, info->dev);
> + break;
> + case H4P_ID_BCM2048:
> + err = request_firmware(fw_entry, FW_NAME_BCM2048, info->dev);
> + break;
> + default:
> + dev_err(info->dev, "Invalid chip type\n");
> + *fw_entry = NULL;
> + err = -EINVAL;
> + }
> +
> + return err;
> +}
> +
> +static void hci_h4p_close_firmware(const struct firmware *fw_entry)
> +{
> + release_firmware(fw_entry);
> +}
> +
> +/* Read fw. Return length of the command. If no more commands in
> + * fw 0 is returned. In error case return value is negative.
> + */
> +static int hci_h4p_read_fw_cmd(struct hci_h4p_info *info, struct sk_buff **skb,
> + const struct firmware *fw_entry, gfp_t how)
> +{
> + unsigned int cmd_len;
> +
> + if (fw_pos >= fw_entry->size)
> + return 0;
> +
> + if (fw_pos + 2 > fw_entry->size) {
> + dev_err(info->dev, "Corrupted firmware image 1\n");
> + return -EMSGSIZE;
> + }
> +
> + cmd_len = fw_entry->data[fw_pos++];
> + cmd_len += fw_entry->data[fw_pos++] << 8;
> + if (cmd_len == 0)
> + return 0;
> +
> + if (fw_pos + cmd_len > fw_entry->size) {
> + dev_err(info->dev, "Corrupted firmware image 2\n");
> + return -EMSGSIZE;
> + }
> +
> + *skb = bt_skb_alloc(cmd_len, how);
> + if (!*skb) {
> + dev_err(info->dev, "Cannot reserve memory for buffer\n");
> + return -ENOMEM;
> + }
> + memcpy(skb_put(*skb, cmd_len), &fw_entry->data[fw_pos], cmd_len);
> +
> + fw_pos += cmd_len;
> +
> + return (*skb)->len;
> +}
> +
> +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue)
> +{
> + const struct firmware *fw_entry = NULL;
> + struct sk_buff *skb = NULL;
> + int err;
> +
> + err = hci_h4p_open_firmware(info, &fw_entry);
> + if (err < 0 || !fw_entry)
> + goto err_clean;
> +
> + while ((err = hci_h4p_read_fw_cmd(info, &skb, fw_entry, GFP_KERNEL))) {
> + if (err < 0 || !skb)
> + goto err_clean;
> +
> + skb_queue_tail(fw_queue, skb);
> + }
> +
> + /* Chip detection code does neg and alive stuff
> + * discard two first skbs */
> + skb = skb_dequeue(fw_queue);
> + if (!skb) {
> + err = -EMSGSIZE;
> + goto err_clean;
> + }
> + kfree_skb(skb);
> + skb = skb_dequeue(fw_queue);
> + if (!skb) {
> + err = -EMSGSIZE;
> + goto err_clean;
> + }
> + kfree_skb(skb);
> +
> +err_clean:
> + hci_h4p_close_firmware(fw_entry);
> + return err;
> +}
> +
> +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue)
> +{
> + int err;
> +
> + switch (info->man_id) {
> + case H4P_ID_CSR:
> + err = hci_h4p_bc4_send_fw(info, fw_queue);
> + break;
> + case H4P_ID_TI1271:
> + err = hci_h4p_ti1273_send_fw(info, fw_queue);
> + break;
> + case H4P_ID_BCM2048:
> + err = hci_h4p_bcm_send_fw(info, fw_queue);
> + break;
> + default:
> + dev_err(info->dev, "Don't know how to send firmware\n");
> + err = -EINVAL;
> + }
> +
> + return err;
> +}
> +
> +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb)
> +{
> + switch (info->man_id) {
> + case H4P_ID_CSR:
> + hci_h4p_bc4_parse_fw_event(info, skb);
> + break;
> + case H4P_ID_TI1271:
> + hci_h4p_ti1273_parse_fw_event(info, skb);
> + break;
> + case H4P_ID_BCM2048:
> + hci_h4p_bcm_parse_fw_event(info, skb);
> + break;
> + default:
> + dev_err(info->dev, "Don't know how to parse fw event\n");
> + info->fw_error = -EINVAL;
> + }
> +
> + return;
> +}
> diff --git a/drivers/bluetooth/nokia_uart.c b/drivers/bluetooth/nokia_uart.c
> new file mode 100644
> index 0000000..0fb57de
> --- /dev/null
> +++ b/drivers/bluetooth/nokia_uart.c
> @@ -0,0 +1,199 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2005, 2006 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +#include <linux/serial_reg.h>
> +#include <linux/delay.h>
> +#include <linux/clk.h>
> +
> +#include <linux/io.h>
> +
> +#include "hci_h4p.h"
> +
> +inline void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val)
> +{
> + __raw_writeb(val, info->uart_base + (offset << 2));
> +}
> +
> +inline u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset)
> +{
> + return __raw_readb(info->uart_base + (offset << 2));
> +}
> +
> +void hci_h4p_set_rts(struct hci_h4p_info *info, int active)
> +{
> + u8 b;
> +
> + b = hci_h4p_inb(info, UART_MCR);
> + if (active)
> + b |= UART_MCR_RTS;
> + else
> + b &= ~UART_MCR_RTS;
> + hci_h4p_outb(info, UART_MCR, b);
> +}
> +
> +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active,
> + int timeout_ms)
> +{
> + unsigned long timeout;
> + int state;
> +
> + timeout = jiffies + msecs_to_jiffies(timeout_ms);
> + for (;;) {
> + state = hci_h4p_inb(info, UART_MSR) & UART_MSR_CTS;
> + if (active) {
> + if (state)
> + return 0;
> + } else {
> + if (!state)
> + return 0;
> + }
> + if (time_after(jiffies, timeout))
> + return -ETIMEDOUT;
> + msleep(1);
> + }
> +}
> +
> +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which)
> +{
> + u8 lcr, b;
> +
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, 0xbf);
> + b = hci_h4p_inb(info, UART_EFR);
> + if (on)
> + b |= which;
> + else
> + b &= ~which;
> + hci_h4p_outb(info, UART_EFR, b);
> + hci_h4p_outb(info, UART_LCR, lcr);
> +}
> +
> +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&info->lock, flags);
> + __hci_h4p_set_auto_ctsrts(info, on, which);
> + spin_unlock_irqrestore(&info->lock, flags);
> +}
> +
> +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed)
> +{
> + unsigned int divisor;
> + u8 lcr, mdr1;
> +
> + BT_DBG("Setting speed %lu", speed);
> +
> + if (speed >= 460800) {
> + divisor = UART_CLOCK / 13 / speed;
> + mdr1 = 3;
> + } else {
> + divisor = UART_CLOCK / 16 / speed;
> + mdr1 = 0;
> + }
> +
> + /* Make sure UART mode is disabled */
> + hci_h4p_outb(info, UART_OMAP_MDR1, 7);
> +
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB); /* Set DLAB */
> + hci_h4p_outb(info, UART_DLL, divisor & 0xff); /* Set speed */
> + hci_h4p_outb(info, UART_DLM, divisor >> 8);
> + hci_h4p_outb(info, UART_LCR, lcr);
> +
> + /* Make sure UART mode is enabled */
> + hci_h4p_outb(info, UART_OMAP_MDR1, mdr1);
> +}
> +
> +int hci_h4p_reset_uart(struct hci_h4p_info *info)
> +{
> + int count = 0;
> +
> + /* Reset the UART */
> + hci_h4p_outb(info, UART_OMAP_SYSC, UART_SYSC_OMAP_RESET);
> + while (!(hci_h4p_inb(info, UART_OMAP_SYSS) & UART_SYSS_RESETDONE)) {
> + if (count++ > 100) {
> + dev_err(info->dev, "hci_h4p: UART reset timeout\n");
> + return -ENODEV;
> + }
> + udelay(1);
> + }
> +
> + return 0;
> +}
> +
> +void hci_h4p_store_regs(struct hci_h4p_info *info)
> +{
> + u16 lcr = 0;
> +
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, 0xBF);
> + info->dll = hci_h4p_inb(info, UART_DLL);
> + info->dlh = hci_h4p_inb(info, UART_DLM);
> + info->efr = hci_h4p_inb(info, UART_EFR);
> + hci_h4p_outb(info, UART_LCR, lcr);
> + info->mdr1 = hci_h4p_inb(info, UART_OMAP_MDR1);
> + info->ier = hci_h4p_inb(info, UART_IER);
> +}
> +
> +void hci_h4p_restore_regs(struct hci_h4p_info *info)
> +{
> + u16 lcr = 0;
> +
> + hci_h4p_init_uart(info);
> +
> + hci_h4p_outb(info, UART_OMAP_MDR1, 7);
> + lcr = hci_h4p_inb(info, UART_LCR);
> + hci_h4p_outb(info, UART_LCR, 0xBF);
> + hci_h4p_outb(info, UART_DLL, info->dll); /* Set speed */
> + hci_h4p_outb(info, UART_DLM, info->dlh);
> + hci_h4p_outb(info, UART_EFR, info->efr);
> + hci_h4p_outb(info, UART_LCR, lcr);
> + hci_h4p_outb(info, UART_OMAP_MDR1, info->mdr1);
> + hci_h4p_outb(info, UART_IER, info->ier);
> +}
> +
> +void hci_h4p_init_uart(struct hci_h4p_info *info)
> +{
> + u8 mcr, efr;
> +
> + /* Enable and setup FIFO */
> + hci_h4p_outb(info, UART_OMAP_MDR1, 0x00);
> +
> + hci_h4p_outb(info, UART_LCR, 0xbf);
> + efr = hci_h4p_inb(info, UART_EFR);
> + hci_h4p_outb(info, UART_EFR, UART_EFR_ECB);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB);
> + mcr = hci_h4p_inb(info, UART_MCR);
> + hci_h4p_outb(info, UART_MCR, UART_MCR_TCRTLR);
> + hci_h4p_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO |
> + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT |
> + (3 << 6) | (0 << 4));
> + hci_h4p_outb(info, UART_LCR, 0xbf);
> + hci_h4p_outb(info, UART_TI752_TLR, 0xed);
> + hci_h4p_outb(info, UART_TI752_TCR, 0xef);
> + hci_h4p_outb(info, UART_EFR, efr);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB);
> + hci_h4p_outb(info, UART_MCR, 0x00);
> + hci_h4p_outb(info, UART_LCR, UART_LCR_WLEN8);
> + hci_h4p_outb(info, UART_IER, UART_IER_RDI);
> + hci_h4p_outb(info, UART_OMAP_SYSC, (1 << 0) | (1 << 2) | (2 << 3));
> +}
> diff --git a/include/linux/platform_data/hci-h4p.h b/include/linux/platform_data/hci-h4p.h
> new file mode 100644
> index 0000000..30d169d
> --- /dev/null
> +++ b/include/linux/platform_data/hci-h4p.h
> @@ -0,0 +1,38 @@
> +/*
> + * This file is part of Nokia H4P bluetooth driver
> + *
> + * Copyright (C) 2010 Nokia Corporation.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
> + * 02110-1301 USA
> + *
> + */
> +
> +
> +/**
> + * struct hci_h4p_platform data - hci_h4p Platform data structure
> + */
> +struct hci_h4p_platform_data {
> + int chip_type;
> + int bt_sysclk;
> + unsigned int bt_wakeup_gpio;
> + unsigned int host_wakeup_gpio;
> + unsigned int reset_gpio;
> + int reset_gpio_shared;
> + unsigned int uart_irq;
> + phys_addr_t uart_base;
> + const char *uart_iclk;
> + const char *uart_fclk;
> + void (*set_pm_limits)(struct device *dev, bool set);
> +};
>
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply
* [PATCH 4/4] android/a2dp: Fix memory leak
From: Andrzej Kaczmarek @ 2014-01-15 21:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Andrzej Kaczmarek
In-Reply-To: <1389819699-12055-1-git-send-email-andrzej.kaczmarek@tieto.com>
audio_ipc_send_rsp_full() does not free buffer passed as parameter
thus it should be freed by caller.
This fixes following Valgrind report:
==1238== 5 bytes in 1 blocks are definitely lost in loss record 22 of 54
==1238== at 0x4896DC8: calloc (in /system/lib/valgrind/vgpreload_memcheck-arm-linux.so)
==1238== by 0x48C5DB7: g_malloc0 (gmem.c:189)
==1238== by 0x1150EF: bt_stream_open (a2dp.c:1177)
==1238== by 0x1116A7: ipc_handle_msg (ipc.c:95)
==1238== by 0x111C11: audio_watch_cb (audio-ipc.c:66)
==1238== by 0x48BD9C7: g_io_unix_dispatch (giounix.c:166)
==1238== by 0x48C2CCB: g_main_context_dispatch (gmain.c:2539)
==1238== by 0x48C2ED9: g_main_context_iterate.isra.19 (gmain.c:3146)
==1238== by 0x48C3167: g_main_loop_run (gmain.c:3340)
==1238== by 0x10B207: main (main.c:436)
---
android/a2dp.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/android/a2dp.c b/android/a2dp.c
index a5ea5a0..a36e9a3 100644
--- a/android/a2dp.c
+++ b/android/a2dp.c
@@ -1181,6 +1181,8 @@ static void bt_stream_open(const void *buf, uint16_t len)
memcpy(rsp->preset->data, setup->preset->data, setup->preset->len);
audio_ipc_send_rsp_full(AUDIO_OP_OPEN_STREAM, len, rsp, -1);
+
+ g_free(rsp);
}
static void bt_stream_close(const void *buf, uint16_t len)
--
1.8.5.2
^ permalink raw reply related
* [PATCH 3/4] android/a2dp: Fix memory leak
From: Andrzej Kaczmarek @ 2014-01-15 21:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Andrzej Kaczmarek
In-Reply-To: <1389819699-12055-1-git-send-email-andrzej.kaczmarek@tieto.com>
avdtp_service_cap_new() makes own copy of data stored in "codec" thus
it should be freed by caller.
This fixes following Valgrind report:
==1238== 6 bytes in 1 blocks are definitely lost in loss record 27 of 54
==1238== at 0x4896DC8: calloc (in /system/lib/valgrind/vgpreload_memcheck-arm-linux.so)
==1238== by 0x48C5DB7: g_malloc0 (gmem.c:189)
==1238== by 0x115B4B: discover_cb (a2dp.c:303)
==1238== by 0x111DE7: finalize_discovery (avdtp.c:933)
==1238== by 0x114441: session_cb (avdtp.c:2556)
==1238== by 0x48BD9C7: g_io_unix_dispatch (giounix.c:166)
==1238== by 0x48C2CCB: g_main_context_dispatch (gmain.c:2539)
==1238== by 0x48C2ED9: g_main_context_iterate.isra.19 (gmain.c:3146)
==1238== by 0x48C3167: g_main_loop_run (gmain.c:3340)
==1238== by 0x10B207: main (main.c:436)
---
android/a2dp.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/android/a2dp.c b/android/a2dp.c
index 35ffe46..a5ea5a0 100644
--- a/android/a2dp.c
+++ b/android/a2dp.c
@@ -309,6 +309,8 @@ static int select_configuration(struct a2dp_device *dev,
sizeof(*codec) + preset->len);
caps = g_slist_append(caps, service);
+ g_free(codec);
+
err = avdtp_set_configuration(dev->session, rsep, endpoint->sep, caps,
&stream);
g_slist_free_full(caps, g_free);
--
1.8.5.2
^ permalink raw reply related
* [PATCH 2/4] android: Fix typo
From: Andrzej Kaczmarek @ 2014-01-15 21:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Andrzej Kaczmarek
In-Reply-To: <1389819699-12055-1-git-send-email-andrzej.kaczmarek@tieto.com>
---
android/bluetoothd-snoop.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/android/bluetoothd-snoop.c b/android/bluetoothd-snoop.c
index 23e0bfd..9312c11 100644
--- a/android/bluetoothd-snoop.c
+++ b/android/bluetoothd-snoop.c
@@ -37,7 +37,7 @@
#include "monitor/mainloop.h"
#include "src/shared/btsnoop.h"
-#define DEAULT_SNOOP_FILE "/sdcard/btsnoop_hci.log"
+#define DEFAULT_SNOOP_FILE "/sdcard/btsnoop_hci.log"
#define MAX_PACKET_SIZE (1486 + 4)
@@ -196,7 +196,7 @@ int main(int argc, char *argv[])
if (argc > 1)
path = argv[1];
else
- path = DEAULT_SNOOP_FILE;
+ path = DEFAULT_SNOOP_FILE;
mainloop_init();
--
1.8.5.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox