From cf1c4a094f46ace5bf00078e2db73491ddf018fe Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 19 Feb 2013 11:35:59 -0800 Subject: netfilter: ipset: timeout values corrupted on set resize If a resize is triggered on a set with timeouts enabled, the timeout values will get corrupted when copying them to the new set. This occured b/c the wrong timeout value is supplied to type_pf_elem_tadd(). This also adds simple debug statement similar to the one in type_pf_resize(). Signed-off-by: Josh Hunt Signed-off-by: Jozsef Kadlecsik --- include/linux/netfilter/ipset/ip_set_ahash.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/netfilter/ipset/ip_set_ahash.h b/include/linux/netfilter/ipset/ip_set_ahash.h index ef9acd3c8450..01d25e6fc792 100644 --- a/include/linux/netfilter/ipset/ip_set_ahash.h +++ b/include/linux/netfilter/ipset/ip_set_ahash.h @@ -854,6 +854,8 @@ type_pf_tresize(struct ip_set *set, bool retried) retry: ret = 0; htable_bits++; + pr_debug("attempt to resize set %s from %u to %u, t %p\n", + set->name, orig->htable_bits, htable_bits, orig); if (!htable_bits) { /* In case we have plenty of memory :-) */ pr_warning("Cannot increase the hashsize of set %s further\n", @@ -873,7 +875,7 @@ retry: data = ahash_tdata(n, j); m = hbucket(t, HKEY(data, h->initval, htable_bits)); ret = type_pf_elem_tadd(m, data, AHASH_MAX(h), 0, - type_pf_data_timeout(data)); + ip_set_timeout_get(type_pf_data_timeout(data))); if (ret < 0) { read_unlock_bh(&set->lock); ahash_destroy(t); -- cgit v1.2.3 From 4d4c4e24cf48400a24d33feffc7cca4f4e8cabe1 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 22 Feb 2013 00:05:07 +0100 Subject: irq: Remove IRQ_EXIT_OFFSET workaround The IRQ_EXIT_OFFSET trick was used to make sure the irq doesn't get preempted after we substract the HARDIRQ_OFFSET until we are entirely done with any code in irq_exit(). This workaround was necessary because some archs may call irq_exit() with irqs enabled and there is still some code in the end of this function that is not covered by the HARDIRQ_OFFSET but want to stay non-preemptible. Now that irq are always disabled in irq_exit(), the whole code is guaranteed not to be preempted. We can thus remove this hack. Signed-off-by: Frederic Weisbecker Cc: Linus Torvalds Cc: Thomas Gleixner Cc: Ingo Molnar Cc: Peter Zijlstra Cc: Paul E. McKenney --- include/linux/hardirq.h | 2 -- kernel/softirq.c | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/hardirq.h b/include/linux/hardirq.h index 29eb805ea4a6..c1d6555d2567 100644 --- a/include/linux/hardirq.h +++ b/include/linux/hardirq.h @@ -118,10 +118,8 @@ #ifdef CONFIG_PREEMPT_COUNT # define preemptible() (preempt_count() == 0 && !irqs_disabled()) -# define IRQ_EXIT_OFFSET (HARDIRQ_OFFSET-1) #else # define preemptible() 0 -# define IRQ_EXIT_OFFSET HARDIRQ_OFFSET #endif #if defined(CONFIG_SMP) || defined(CONFIG_GENERIC_HARDIRQS) diff --git a/kernel/softirq.c b/kernel/softirq.c index 24a921bcf04f..f42ff97e1f8f 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -343,7 +343,7 @@ void irq_exit(void) account_irq_exit_time(current); trace_hardirq_exit(); - sub_preempt_count(IRQ_EXIT_OFFSET); + sub_preempt_count(HARDIRQ_OFFSET); if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); @@ -353,7 +353,6 @@ void irq_exit(void) tick_nohz_irq_exit(); #endif rcu_irq_exit(); - sched_preempt_enable_no_resched(); #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED local_irq_restore(flags); #endif -- cgit v1.2.3 From 46c498c2cdee5efe44f617bcd4f388179be36115 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 26 Feb 2013 18:44:33 +0100 Subject: stop_machine: Mark per cpu stopper enabled early commit 14e568e78 (stop_machine: Use smpboot threads) introduced the following regression: Before this commit the stopper enabled bit was set in the online notifier. CPU0 CPU1 cpu_up cpu online hotplug_notifier(ONLINE) stopper(CPU1)->enabled = true; ... stop_machine() The conversion to smpboot threads moved the enablement to the wakeup path of the parked thread. The majority of users seem to have the following working order: CPU0 CPU1 cpu_up cpu online unpark_threads() wakeup(stopper[CPU1]) .... stopper thread runs stopper(CPU1)->enabled = true; stop_machine() But Konrad and Sander have observed: CPU0 CPU1 cpu_up cpu online unpark_threads() wakeup(stopper[CPU1]) .... stop_machine() stopper thread runs stopper(CPU1)->enabled = true; Now the stop machinery kicks CPU0 into the stop loop, where it gets stuck forever because the queue code saw stopper(CPU1)->enabled == false, so CPU0 waits for CPU1 to enter stomp_machine, but the CPU1 stopper work got discarded due to enabled == false. Add a pre_unpark function to the smpboot thread descriptor and call it before waking the thread. This fixes the problem at hand, but the stop_machine code should be more robust. The stopper->enabled flag smells fishy at best. Thanks to Konrad for going through a loop of debug patches and providing the information to decode this issue. Reported-and-tested-by: Konrad Rzeszutek Wilk Reported-and-tested-by: Sander Eikelenboom Cc: Srivatsa S. Bhat Cc: Rusty Russell Link: http://lkml.kernel.org/r/alpine.LFD.2.02.1302261843240.22263@ionos Signed-off-by: Thomas Gleixner --- include/linux/smpboot.h | 4 ++++ kernel/smpboot.c | 2 ++ kernel/stop_machine.c | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/smpboot.h b/include/linux/smpboot.h index c65dee059913..13e929679550 100644 --- a/include/linux/smpboot.h +++ b/include/linux/smpboot.h @@ -24,6 +24,9 @@ struct smpboot_thread_data; * parked (cpu offline) * @unpark: Optional unpark function, called when the thread is * unparked (cpu online) + * @pre_unpark: Optional unpark function, called before the thread is + * unparked (cpu online). This is not guaranteed to be + * called on the target cpu of the thread. Careful! * @selfparking: Thread is not parked by the park function. * @thread_comm: The base name of the thread */ @@ -37,6 +40,7 @@ struct smp_hotplug_thread { void (*cleanup)(unsigned int cpu, bool online); void (*park)(unsigned int cpu); void (*unpark)(unsigned int cpu); + void (*pre_unpark)(unsigned int cpu); bool selfparking; const char *thread_comm; }; diff --git a/kernel/smpboot.c b/kernel/smpboot.c index d4abac261779..8eaed9aa9cf0 100644 --- a/kernel/smpboot.c +++ b/kernel/smpboot.c @@ -209,6 +209,8 @@ static void smpboot_unpark_thread(struct smp_hotplug_thread *ht, unsigned int cp { struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu); + if (ht->pre_unpark) + ht->pre_unpark(cpu); kthread_unpark(tsk); } diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c index 95d178c62d5a..c09f2955ae30 100644 --- a/kernel/stop_machine.c +++ b/kernel/stop_machine.c @@ -336,7 +336,7 @@ static struct smp_hotplug_thread cpu_stop_threads = { .create = cpu_stop_create, .setup = cpu_stop_unpark, .park = cpu_stop_park, - .unpark = cpu_stop_unpark, + .pre_unpark = cpu_stop_unpark, .selfparking = true, }; -- cgit v1.2.3 From 79ffef1fe213851f44bfccf037170a140e929f85 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 27 Feb 2013 07:05:03 +0000 Subject: tcp: avoid wakeups for pure ACK TCP prequeue mechanism purpose is to let incoming packets being processed by the thread currently blocked in tcp_recvmsg(), instead of behalf of the softirq handler, to better adapt flow control on receiver host capacity to schedule the consumer. But in typical request/answer workloads, we send request, then block to receive the answer. And before the actual answer, TCP stack receives the ACK packets acknowledging the request. Processing pure ACK on behalf of the thread blocked in tcp_recvmsg() is a waste of resources, as thread has to immediately sleep again because it got no payload. This patch avoids the extra context switches and scheduler overhead. Before patch : a:~# echo 0 >/proc/sys/net/ipv4/tcp_low_latency a:~# perf stat ./super_netperf 300 -t TCP_RR -l 10 -H 7.7.7.84 -- -r 8k,8k 231676 Performance counter stats for './super_netperf 300 -t TCP_RR -l 10 -H 7.7.7.84 -- -r 8k,8k': 116251.501765 task-clock # 11.369 CPUs utilized 5,025,463 context-switches # 0.043 M/sec 1,074,511 CPU-migrations # 0.009 M/sec 216,923 page-faults # 0.002 M/sec 311,636,972,396 cycles # 2.681 GHz 260,507,138,069 stalled-cycles-frontend # 83.59% frontend cycles idle 155,590,092,840 stalled-cycles-backend # 49.93% backend cycles idle 100,101,255,411 instructions # 0.32 insns per cycle # 2.60 stalled cycles per insn 16,535,930,999 branches # 142.243 M/sec 646,483,591 branch-misses # 3.91% of all branches 10.225482774 seconds time elapsed After patch : a:~# echo 0 >/proc/sys/net/ipv4/tcp_low_latency a:~# perf stat ./super_netperf 300 -t TCP_RR -l 10 -H 7.7.7.84 -- -r 8k,8k 233297 Performance counter stats for './super_netperf 300 -t TCP_RR -l 10 -H 7.7.7.84 -- -r 8k,8k': 91084.870855 task-clock # 8.887 CPUs utilized 2,485,916 context-switches # 0.027 M/sec 815,520 CPU-migrations # 0.009 M/sec 216,932 page-faults # 0.002 M/sec 245,195,022,629 cycles # 2.692 GHz 202,635,777,041 stalled-cycles-frontend # 82.64% frontend cycles idle 124,280,372,407 stalled-cycles-backend # 50.69% backend cycles idle 83,457,289,618 instructions # 0.34 insns per cycle # 2.43 stalled cycles per insn 13,431,472,361 branches # 147.461 M/sec 504,470,665 branch-misses # 3.76% of all branches 10.249594448 seconds time elapsed Signed-off-by: Eric Dumazet Cc: Neal Cardwell Cc: Tom Herbert Cc: Yuchung Cheng Cc: Andi Kleen Signed-off-by: David S. Miller --- include/net/tcp.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include') diff --git a/include/net/tcp.h b/include/net/tcp.h index 23f2e98d4b65..cf0694d4ad60 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1045,6 +1045,10 @@ static inline bool tcp_prequeue(struct sock *sk, struct sk_buff *skb) if (sysctl_tcp_low_latency || !tp->ucopy.task) return false; + if (skb->len <= tcp_hdrlen(skb) && + skb_queue_len(&tp->ucopy.prequeue) == 0) + return false; + __skb_queue_tail(&tp->ucopy.prequeue, skb); tp->ucopy.memory += skb->truesize; if (tp->ucopy.memory > sk->sk_rcvbuf) { -- cgit v1.2.3 From 8524982847ff00b66ffb89314c342c51f4138ee7 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Mon, 18 Feb 2013 21:47:45 +0100 Subject: ssb: fix unaligned access to mac address The mac address should be aligned to u16 to prevent an unaligned access in drivers/ssb/pci.c where it is casted to __be16. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- include/linux/ssb/ssb.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h index 22958d68ecfe..8b1322296fed 100644 --- a/include/linux/ssb/ssb.h +++ b/include/linux/ssb/ssb.h @@ -26,9 +26,9 @@ struct ssb_sprom_core_pwr_info { struct ssb_sprom { u8 revision; - u8 il0mac[6]; /* MAC address for 802.11b/g */ - u8 et0mac[6]; /* MAC address for Ethernet */ - u8 et1mac[6]; /* MAC address for 802.11a */ + u8 il0mac[6] __aligned(sizeof(u16)); /* MAC address for 802.11b/g */ + u8 et0mac[6] __aligned(sizeof(u16)); /* MAC address for Ethernet */ + u8 et1mac[6] __aligned(sizeof(u16)); /* MAC address for 802.11a */ u8 et0phyaddr; /* MII address for enet0 */ u8 et1phyaddr; /* MII address for enet1 */ u8 et0mdcport; /* MDIO for enet0 */ -- cgit v1.2.3 From bed71748346ae0807c7f7a2913965508dbd61403 Mon Sep 17 00:00:00 2001 From: Andre Guedes Date: Wed, 30 Jan 2013 11:50:56 -0300 Subject: Bluetooth: Rename hci_acl_disconn As hci_acl_disconn function basically sends the HCI Disconnect Command and it is used to disconnect ACL, SCO and LE links, renaming it to hci_disconnect is more suitable. Signed-off-by: Andre Guedes Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 2 +- net/bluetooth/hci_conn.c | 4 ++-- net/bluetooth/hci_core.c | 2 +- net/bluetooth/hci_event.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 90cf75afcb02..787d3b9bbd58 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -574,7 +574,7 @@ static inline struct hci_conn *hci_conn_hash_lookup_state(struct hci_dev *hdev, return NULL; } -void hci_acl_disconn(struct hci_conn *conn, __u8 reason); +void hci_disconnect(struct hci_conn *conn, __u8 reason); void hci_setup_sync(struct hci_conn *conn, __u16 handle); void hci_sco_setup(struct hci_conn *conn, __u8 status); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 4925a02ae7e4..b9f90169940b 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -117,7 +117,7 @@ static void hci_acl_create_connection_cancel(struct hci_conn *conn) hci_send_cmd(conn->hdev, HCI_OP_CREATE_CONN_CANCEL, sizeof(cp), &cp); } -void hci_acl_disconn(struct hci_conn *conn, __u8 reason) +void hci_disconnect(struct hci_conn *conn, __u8 reason) { struct hci_cp_disconnect cp; @@ -253,7 +253,7 @@ static void hci_conn_disconnect(struct hci_conn *conn) hci_amp_disconn(conn, reason); break; default: - hci_acl_disconn(conn, reason); + hci_disconnect(conn, reason); break; } } diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 60793e7b768b..4cb46c24a749 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2398,7 +2398,7 @@ static void hci_link_tx_to(struct hci_dev *hdev, __u8 type) if (c->type == type && c->sent) { BT_ERR("%s killing stalled connection %pMR", hdev->name, &c->dst); - hci_acl_disconn(c, HCI_ERROR_REMOTE_USER_TERM); + hci_disconnect(c, HCI_ERROR_REMOTE_USER_TERM); } } diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 477726a63512..5892e54835a1 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -2399,7 +2399,7 @@ static void hci_encrypt_change_evt(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->flags); if (ev->status && conn->state == BT_CONNECTED) { - hci_acl_disconn(conn, HCI_ERROR_AUTH_FAILURE); + hci_disconnect(conn, HCI_ERROR_AUTH_FAILURE); hci_conn_put(conn); goto unlock; } @@ -3472,7 +3472,7 @@ static void hci_key_refresh_complete_evt(struct hci_dev *hdev, clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->flags); if (ev->status && conn->state == BT_CONNECTED) { - hci_acl_disconn(conn, HCI_ERROR_AUTH_FAILURE); + hci_disconnect(conn, HCI_ERROR_AUTH_FAILURE); hci_conn_put(conn); goto unlock; } -- cgit v1.2.3 From be9f97f04565a6c438b7521ad679870d25645475 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Sun, 24 Feb 2013 19:36:52 +0100 Subject: Bluetooth: change bt_sock_unregister() to return void There is no reason a caller ever wants to check the return type of this call. _Iff_ a user successfully called bt_sock_register(), they're allowed to call bt_sock_unregister(). All other calls in the kernel (device_del, device_unregister, kfree(), ..) that are logically equivalent return void. Lets not make callers think they have to check the return type of this call and instead simply return void. We guarantee that after bt_sock_unregister() is called, the socket type _is_ unregistered. If that is not what the caller wants, they're using the wrong function, anyway. Signed-off-by: David Herrmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/bluetooth.h | 2 +- net/bluetooth/af_bluetooth.c | 15 +++------------ 2 files changed, 4 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 9531beee09b5..5f51bef13e61 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -232,7 +232,7 @@ struct bt_sock_list { }; int bt_sock_register(int proto, const struct net_proto_family *ops); -int bt_sock_unregister(int proto); +void bt_sock_unregister(int proto); void bt_sock_link(struct bt_sock_list *l, struct sock *s); void bt_sock_unlink(struct bt_sock_list *l, struct sock *s); int bt_sock_recvmsg(struct kiocb *iocb, struct socket *sock, diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index d3ee69b35a78..81598e588f7f 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -92,23 +92,14 @@ int bt_sock_register(int proto, const struct net_proto_family *ops) } EXPORT_SYMBOL(bt_sock_register); -int bt_sock_unregister(int proto) +void bt_sock_unregister(int proto) { - int err = 0; - if (proto < 0 || proto >= BT_MAX_PROTO) - return -EINVAL; + return; write_lock(&bt_proto_lock); - - if (!bt_proto[proto]) - err = -ENOENT; - else - bt_proto[proto] = NULL; - + bt_proto[proto] = NULL; write_unlock(&bt_proto_lock); - - return err; } EXPORT_SYMBOL(bt_sock_unregister); -- cgit v1.2.3 From 8ff52f7d04d9cc31f1e81dcf9a2ba6335ed34905 Mon Sep 17 00:00:00 2001 From: Dean Jenkins Date: Thu, 28 Feb 2013 14:21:55 +0000 Subject: Bluetooth: Return RFCOMM session ptrs to avoid freed session Unfortunately, the design retains local copies of the s RFCOMM session pointer in various code blocks and this invites the erroneous access to a freed RFCOMM session structure. Therefore, return the RFCOMM session pointer back up the call stack to avoid accessing a freed RFCOMM session structure. When the RFCOMM session is deleted, NULL is passed up the call stack. If active DLCs exist when the rfcomm session is terminating, avoid a memory leak of rfcomm_dlc structures by ensuring that rfcomm_session_close() is used instead of rfcomm_session_del(). Signed-off-by: Dean Jenkins Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/rfcomm.h | 3 +- net/bluetooth/rfcomm/core.c | 106 ++++++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 51 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index e2e3ecad1008..a4e38ead2282 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -278,7 +278,8 @@ void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, static inline void rfcomm_session_hold(struct rfcomm_session *s) { - atomic_inc(&s->refcnt); + if (s) + atomic_inc(&s->refcnt); } /* ---- RFCOMM sockets ---- */ diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index d9e97cf1792b..2b5c543638ba 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -69,7 +69,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, u8 sec_level, int *err); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); -static void rfcomm_session_del(struct rfcomm_session *s); +static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s); /* ---- RFCOMM frame parsing macros ---- */ #define __get_dlci(b) ((b & 0xfc) >> 2) @@ -108,10 +108,12 @@ static void rfcomm_schedule(void) wake_up_process(rfcomm_thread); } -static void rfcomm_session_put(struct rfcomm_session *s) +static struct rfcomm_session *rfcomm_session_put(struct rfcomm_session *s) { - if (atomic_dec_and_test(&s->refcnt)) - rfcomm_session_del(s); + if (s && atomic_dec_and_test(&s->refcnt)) + s = rfcomm_session_del(s); + + return s; } /* ---- RFCOMM FCS computation ---- */ @@ -631,7 +633,7 @@ static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state) return s; } -static void rfcomm_session_del(struct rfcomm_session *s) +static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s) { int state = s->state; @@ -648,6 +650,8 @@ static void rfcomm_session_del(struct rfcomm_session *s) if (state != BT_LISTEN) module_put(THIS_MODULE); + + return NULL; } static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst) @@ -666,7 +670,8 @@ static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst) return NULL; } -static void rfcomm_session_close(struct rfcomm_session *s, int err) +static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, + int err) { struct rfcomm_dlc *d; struct list_head *p, *n; @@ -685,7 +690,7 @@ static void rfcomm_session_close(struct rfcomm_session *s, int err) } rfcomm_session_clear_timer(s); - rfcomm_session_put(s); + return rfcomm_session_put(s); } static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, @@ -737,8 +742,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, if (*err == 0 || *err == -EINPROGRESS) return s; - rfcomm_session_del(s); - return NULL; + return rfcomm_session_del(s); failed: sock_release(sock); @@ -1127,7 +1131,7 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr) } /* ---- RFCOMM frame reception ---- */ -static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) +static struct rfcomm_session *rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) { BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); @@ -1136,7 +1140,7 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); if (!d) { rfcomm_send_dm(s, dlci); - return 0; + return s; } switch (d->state) { @@ -1172,25 +1176,14 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) break; case BT_DISCONN: - /* rfcomm_session_put is called later so don't do - * anything here otherwise we will mess up the session - * reference counter: - * - * (a) when we are the initiator dlc_unlink will drive - * the reference counter to 0 (there is no initial put - * after session_add) - * - * (b) when we are not the initiator rfcomm_rx_process - * will explicitly call put to balance the initial hold - * done after session add. - */ + s = rfcomm_session_close(s, ECONNRESET); break; } } - return 0; + return s; } -static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci) +static struct rfcomm_session *rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci) { int err = 0; @@ -1215,12 +1208,13 @@ static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci) err = ECONNRESET; s->state = BT_CLOSED; - rfcomm_session_close(s, err); + s = rfcomm_session_close(s, err); } - return 0; + return s; } -static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci) +static struct rfcomm_session *rfcomm_recv_disc(struct rfcomm_session *s, + u8 dlci) { int err = 0; @@ -1250,10 +1244,9 @@ static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci) err = ECONNRESET; s->state = BT_CLOSED; - rfcomm_session_close(s, err); + s = rfcomm_session_close(s, err); } - - return 0; + return s; } void rfcomm_dlc_accept(struct rfcomm_dlc *d) @@ -1674,11 +1667,18 @@ drop: return 0; } -static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) +static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s, + struct sk_buff *skb) { struct rfcomm_hdr *hdr = (void *) skb->data; u8 type, dlci, fcs; + if (!s) { + /* no session, so free socket data */ + kfree_skb(skb); + return s; + } + dlci = __get_dlci(hdr->addr); type = __get_type(hdr->ctrl); @@ -1689,7 +1689,7 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) if (__check_fcs(skb->data, type, fcs)) { BT_ERR("bad checksum in packet"); kfree_skb(skb); - return -EILSEQ; + return s; } if (__test_ea(hdr->len)) @@ -1705,22 +1705,23 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) case RFCOMM_DISC: if (__test_pf(hdr->ctrl)) - rfcomm_recv_disc(s, dlci); + s = rfcomm_recv_disc(s, dlci); break; case RFCOMM_UA: if (__test_pf(hdr->ctrl)) - rfcomm_recv_ua(s, dlci); + s = rfcomm_recv_ua(s, dlci); break; case RFCOMM_DM: - rfcomm_recv_dm(s, dlci); + s = rfcomm_recv_dm(s, dlci); break; case RFCOMM_UIH: - if (dlci) - return rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb); - + if (dlci) { + rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb); + return s; + } rfcomm_recv_mcc(s, skb); break; @@ -1729,7 +1730,7 @@ static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) break; } kfree_skb(skb); - return 0; + return s; } /* ---- Connection and data processing ---- */ @@ -1866,7 +1867,7 @@ static void rfcomm_process_dlcs(struct rfcomm_session *s) } } -static void rfcomm_process_rx(struct rfcomm_session *s) +static struct rfcomm_session *rfcomm_process_rx(struct rfcomm_session *s) { struct socket *sock = s->sock; struct sock *sk = sock->sk; @@ -1878,17 +1879,20 @@ static void rfcomm_process_rx(struct rfcomm_session *s) while ((skb = skb_dequeue(&sk->sk_receive_queue))) { skb_orphan(skb); if (!skb_linearize(skb)) - rfcomm_recv_frame(s, skb); + s = rfcomm_recv_frame(s, skb); else kfree_skb(skb); } - if (sk->sk_state == BT_CLOSED) { + if (s && (sk->sk_state == BT_CLOSED)) { if (!s->initiator) - rfcomm_session_put(s); + s = rfcomm_session_put(s); - rfcomm_session_close(s, sk->sk_err); + if (s) + s = rfcomm_session_close(s, sk->sk_err); } + + return s; } static void rfcomm_accept_connection(struct rfcomm_session *s) @@ -1925,7 +1929,7 @@ static void rfcomm_accept_connection(struct rfcomm_session *s) sock_release(nsock); } -static void rfcomm_check_connection(struct rfcomm_session *s) +static struct rfcomm_session *rfcomm_check_connection(struct rfcomm_session *s) { struct sock *sk = s->sock->sk; @@ -1944,9 +1948,10 @@ static void rfcomm_check_connection(struct rfcomm_session *s) case BT_CLOSED: s->state = BT_CLOSED; - rfcomm_session_close(s, sk->sk_err); + s = rfcomm_session_close(s, sk->sk_err); break; } + return s; } static void rfcomm_process_sessions(void) @@ -1975,15 +1980,16 @@ static void rfcomm_process_sessions(void) switch (s->state) { case BT_BOUND: - rfcomm_check_connection(s); + s = rfcomm_check_connection(s); break; default: - rfcomm_process_rx(s); + s = rfcomm_process_rx(s); break; } - rfcomm_process_dlcs(s); + if (s) + rfcomm_process_dlcs(s); rfcomm_session_put(s); } -- cgit v1.2.3 From 08c30aca9e698faddebd34f81e1196295f9dc063 Mon Sep 17 00:00:00 2001 From: Dean Jenkins Date: Thu, 28 Feb 2013 14:21:56 +0000 Subject: Bluetooth: Remove RFCOMM session refcnt Previous commits have improved the handling of the RFCOMM session timer and the RFCOMM session pointers such that freed RFCOMM session structures should no longer be erroneously accessed. The RFCOMM session refcnt now has no purpose and will be deleted by this commit. Note that the RFCOMM session is now deleted as soon as the RFCOMM control channel link is no longer required. This makes the lifetime of the RFCOMM session deterministic and absolute. Previously with the refcnt, there was uncertainty about when the session structure would be deleted because the relative refcnt prevented the session structure from being deleted at will. It was noted that the refcnt could malfunction under very heavy real-time processor loading in embedded SMP environments. This could cause premature RFCOMM session deletion or double session deletion that could result in kernel crashes. Removal of the refcnt prevents this issue. There are 4 connection / disconnection RFCOMM session scenarios: host initiated control link ---> host disconnected control link host initiated ctrl link ---> remote device disconnected ctrl link remote device initiated ctrl link ---> host disconnected ctrl link remote device initiated ctrl link ---> remote device disc'ed ctrl link The control channel connection procedures are independent of the disconnection procedures. Strangely, the RFCOMM session refcnt was applying special treatment so erroneously combining connection and disconnection events. This commit fixes this issue by removing some session code that used the "initiator" member of the session structure that was intended for use with the data channels. Signed-off-by: Dean Jenkins Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/rfcomm.h | 7 ------- net/bluetooth/rfcomm/core.c | 43 +++++------------------------------------- 2 files changed, 5 insertions(+), 45 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index a4e38ead2282..7afd4199d6b6 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -158,7 +158,6 @@ struct rfcomm_session { struct timer_list timer; unsigned long state; unsigned long flags; - atomic_t refcnt; int initiator; /* Default DLC parameters */ @@ -276,12 +275,6 @@ static inline void rfcomm_dlc_unthrottle(struct rfcomm_dlc *d) void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *dst); -static inline void rfcomm_session_hold(struct rfcomm_session *s) -{ - if (s) - atomic_inc(&s->refcnt); -} - /* ---- RFCOMM sockets ---- */ struct sockaddr_rc { sa_family_t rc_family; diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 2b5c543638ba..75b7bbd8acb7 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -108,14 +108,6 @@ static void rfcomm_schedule(void) wake_up_process(rfcomm_thread); } -static struct rfcomm_session *rfcomm_session_put(struct rfcomm_session *s) -{ - if (s && atomic_dec_and_test(&s->refcnt)) - s = rfcomm_session_del(s); - - return s; -} - /* ---- RFCOMM FCS computation ---- */ /* reversed, 8-bit, poly=0x07 */ @@ -251,16 +243,14 @@ static void rfcomm_session_set_timer(struct rfcomm_session *s, long timeout) { BT_DBG("session %p state %ld timeout %ld", s, s->state, timeout); - if (!mod_timer(&s->timer, jiffies + timeout)) - rfcomm_session_hold(s); + mod_timer(&s->timer, jiffies + timeout); } static void rfcomm_session_clear_timer(struct rfcomm_session *s) { BT_DBG("session %p state %ld", s, s->state); - if (del_timer_sync(&s->timer)) - rfcomm_session_put(s); + del_timer_sync(&s->timer); } /* ---- RFCOMM DLCs ---- */ @@ -338,8 +328,6 @@ static void rfcomm_dlc_link(struct rfcomm_session *s, struct rfcomm_dlc *d) { BT_DBG("dlc %p session %p", d, s); - rfcomm_session_hold(s); - rfcomm_session_clear_timer(s); rfcomm_dlc_hold(d); list_add(&d->list, &s->dlcs); @@ -358,8 +346,6 @@ static void rfcomm_dlc_unlink(struct rfcomm_dlc *d) if (list_empty(&s->dlcs)) rfcomm_session_set_timer(s, RFCOMM_IDLE_TIMEOUT); - - rfcomm_session_put(s); } static struct rfcomm_dlc *rfcomm_dlc_get(struct rfcomm_session *s, u8 dlci) @@ -678,8 +664,6 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, BT_DBG("session %p state %ld err %d", s, s->state, err); - rfcomm_session_hold(s); - s->state = BT_CLOSED; /* Close all dlcs */ @@ -690,7 +674,7 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, } rfcomm_session_clear_timer(s); - return rfcomm_session_put(s); + return rfcomm_session_del(s); } static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, @@ -1884,13 +1868,8 @@ static struct rfcomm_session *rfcomm_process_rx(struct rfcomm_session *s) kfree_skb(skb); } - if (s && (sk->sk_state == BT_CLOSED)) { - if (!s->initiator) - s = rfcomm_session_put(s); - - if (s) - s = rfcomm_session_close(s, sk->sk_err); - } + if (s && (sk->sk_state == BT_CLOSED)) + s = rfcomm_session_close(s, sk->sk_err); return s; } @@ -1917,8 +1896,6 @@ static void rfcomm_accept_connection(struct rfcomm_session *s) s = rfcomm_session_add(nsock, BT_OPEN); if (s) { - rfcomm_session_hold(s); - /* We should adjust MTU on incoming sessions. * L2CAP MTU minus UIH header and FCS. */ s->mtu = min(l2cap_pi(nsock->sk)->chan->omtu, @@ -1967,7 +1944,6 @@ static void rfcomm_process_sessions(void) if (test_and_clear_bit(RFCOMM_TIMED_OUT, &s->flags)) { s->state = BT_DISCONN; rfcomm_send_disc(s, 0); - rfcomm_session_put(s); continue; } @@ -1976,8 +1952,6 @@ static void rfcomm_process_sessions(void) continue; } - rfcomm_session_hold(s); - switch (s->state) { case BT_BOUND: s = rfcomm_check_connection(s); @@ -1990,8 +1964,6 @@ static void rfcomm_process_sessions(void) if (s) rfcomm_process_dlcs(s); - - rfcomm_session_put(s); } rfcomm_unlock(); @@ -2041,7 +2013,6 @@ static int rfcomm_add_listener(bdaddr_t *ba) if (!s) goto failed; - rfcomm_session_hold(s); return 0; failed: sock_release(sock); @@ -2099,8 +2070,6 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) if (!s) return; - rfcomm_session_hold(s); - list_for_each_safe(p, n, &s->dlcs) { d = list_entry(p, struct rfcomm_dlc, list); @@ -2132,8 +2101,6 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) set_bit(RFCOMM_AUTH_REJECT, &d->flags); } - rfcomm_session_put(s); - rfcomm_schedule(); } -- cgit v1.2.3 From 3119ae9599e5cdc1b9838563905c500b582ab6a5 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Tue, 5 Mar 2013 20:37:44 +0200 Subject: Bluetooth: Add initial skeleton for asynchronous HCI requests This patch adds the initial definitions and functions for asynchronous HCI requests. Asynchronous requests are essentially a group of HCI commands together with an optional completion callback. The request is tracked through the already existing command queue by having the necessary context information as part of the control buffer of each skb. The only information needed in the skb control buffer is a flag for indicating that the skb is the start of a request as well as the optional complete callback that should be used when the request is complete (this will be found in the last skb of the request). Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/bluetooth.h | 10 ++++++++++ include/net/bluetooth/hci_core.h | 8 ++++++++ net/bluetooth/hci_core.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) (limited to 'include') diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 5f51bef13e61..ed6e9552252e 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -260,12 +260,22 @@ struct l2cap_ctrl { __u8 retries; }; +struct hci_dev; + +typedef void (*hci_req_complete_t)(struct hci_dev *hdev, u8 status); + +struct hci_req_ctrl { + bool start; + hci_req_complete_t complete; +}; + struct bt_skb_cb { __u8 pkt_type; __u8 incoming; __u16 expect; __u8 force_active; struct l2cap_ctrl control; + struct hci_req_ctrl req; }; #define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb)) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 787d3b9bbd58..7191217c6bd1 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1041,6 +1041,14 @@ static inline u16 eir_append_data(u8 *eir, u16 eir_len, u8 type, u8 *data, int hci_register_cb(struct hci_cb *hcb); int hci_unregister_cb(struct hci_cb *hcb); +struct hci_request { + struct hci_dev *hdev; + struct sk_buff_head cmd_q; +}; + +void hci_req_init(struct hci_request *req, struct hci_dev *hdev); +int hci_req_run(struct hci_request *req, hci_req_complete_t complete); + int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param); void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags); void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 6ab38fecf1fe..94b08aa9a081 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2439,6 +2439,36 @@ static int hci_send_frame(struct sk_buff *skb) return hdev->send(skb); } +void hci_req_init(struct hci_request *req, struct hci_dev *hdev) +{ + skb_queue_head_init(&req->cmd_q); + req->hdev = hdev; +} + +int hci_req_run(struct hci_request *req, hci_req_complete_t complete) +{ + struct hci_dev *hdev = req->hdev; + struct sk_buff *skb; + unsigned long flags; + + BT_DBG("length %u", skb_queue_len(&req->cmd_q)); + + /* Do not allow empty requests */ + if (skb_queue_empty(&req->cmd_q)) + return -EINVAL; + + skb = skb_peek_tail(&req->cmd_q); + bt_cb(skb)->req.complete = complete; + + spin_lock_irqsave(&hdev->cmd_q.lock, flags); + skb_queue_splice_tail(&req->cmd_q, &hdev->cmd_q); + spin_unlock_irqrestore(&hdev->cmd_q.lock, flags); + + queue_work(hdev->workqueue, &hdev->cmd_work); + + return 0; +} + /* Send HCI command */ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) { -- cgit v1.2.3 From 71c76a170e979d60e01bd093c9b79e3adeb710cc Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Tue, 5 Mar 2013 20:37:46 +0200 Subject: Bluetooth: Introduce new hci_req_add function This function is analogous to hci_send_cmd() but instead of directly queuing the command to hdev->cmd_q it adds it to the local queue of the asynchronous HCI request being build (inside struct hci_request). This is the main function used for building asynchronous requests and there should be one or more calls to it between calls to hci_req_init and hci_req_run. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_core.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 7191217c6bd1..67fe661259ba 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1048,6 +1048,7 @@ struct hci_request { void hci_req_init(struct hci_request *req, struct hci_dev *hdev); int hci_req_run(struct hci_request *req, hci_req_complete_t complete); +int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param); int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param); void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d2edcc4643c3..6e6a9dd8a155 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2517,6 +2517,28 @@ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) return 0; } +/* Queue a command to an asynchronous HCI request */ +int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param) +{ + struct hci_dev *hdev = req->hdev; + struct sk_buff *skb; + + BT_DBG("%s opcode 0x%4.4x plen %d", hdev->name, opcode, plen); + + skb = hci_prepare_cmd(hdev, opcode, plen, param); + if (!skb) { + BT_ERR("%s no memory for command", hdev->name); + return -ENOMEM; + } + + if (skb_queue_empty(&req->cmd_q)) + bt_cb(skb)->req.start = true; + + skb_queue_tail(&req->cmd_q, skb); + + return 0; +} + /* Get data from the previously sent command */ void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode) { -- cgit v1.2.3 From 9238f36a5a5097018b90baa42c473d2f916a46f5 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Tue, 5 Mar 2013 20:37:48 +0200 Subject: Bluetooth: Add request cmd_complete and cmd_status functions This patch introduces functions to process the HCI request state when receiving HCI Command Status or Command Complete events. Some HCI commands, like Inquiry do not result in a Command complete event so special handling is needed for them. Inquiry is a particularly important one since it is the only forseeable "non-cmd_complete" command that will make good use of the request functionality, and its completion is either indicated by an Inquiry Complete event of a successful Command Complete for HCI_Inquiry_Cancel. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 2 + net/bluetooth/hci_core.c | 85 ++++++++++++++++++++++++++++++++++++++++ net/bluetooth/hci_event.c | 7 ++++ 3 files changed, 94 insertions(+) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 67fe661259ba..d732d6894adc 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1049,6 +1049,8 @@ struct hci_request { void hci_req_init(struct hci_request *req, struct hci_dev *hdev); int hci_req_run(struct hci_request *req, hci_req_complete_t complete); int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param); +void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status); +void hci_req_cmd_status(struct hci_dev *hdev, u16 opcode, u8 status); int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param); void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 4f8142bdf655..0ada2ec36e7b 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3208,6 +3208,91 @@ static void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb) kfree_skb(skb); } +static bool hci_req_is_complete(struct hci_dev *hdev) +{ + struct sk_buff *skb; + + skb = skb_peek(&hdev->cmd_q); + if (!skb) + return true; + + return bt_cb(skb)->req.start; +} + +void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status) +{ + hci_req_complete_t req_complete = NULL; + struct sk_buff *skb; + unsigned long flags; + + BT_DBG("opcode 0x%04x status 0x%02x", opcode, status); + + /* Check that the completed command really matches the last one + * that was sent. + */ + if (!hci_sent_cmd_data(hdev, opcode)) + return; + + /* If the command succeeded and there's still more commands in + * this request the request is not yet complete. + */ + if (!status && !hci_req_is_complete(hdev)) + return; + + /* If this was the last command in a request the complete + * callback would be found in hdev->sent_cmd instead of the + * command queue (hdev->cmd_q). + */ + if (hdev->sent_cmd) { + req_complete = bt_cb(hdev->sent_cmd)->req.complete; + if (req_complete) + goto call_complete; + } + + /* Remove all pending commands belonging to this request */ + spin_lock_irqsave(&hdev->cmd_q.lock, flags); + while ((skb = __skb_dequeue(&hdev->cmd_q))) { + if (bt_cb(skb)->req.start) { + __skb_queue_head(&hdev->cmd_q, skb); + break; + } + + req_complete = bt_cb(skb)->req.complete; + kfree_skb(skb); + } + spin_unlock_irqrestore(&hdev->cmd_q.lock, flags); + +call_complete: + if (req_complete) + req_complete(hdev, status); +} + +void hci_req_cmd_status(struct hci_dev *hdev, u16 opcode, u8 status) +{ + hci_req_complete_t req_complete = NULL; + + BT_DBG("opcode 0x%04x status 0x%02x", opcode, status); + + if (status) { + hci_req_cmd_complete(hdev, opcode, status); + return; + } + + /* No need to handle success status if there are more commands */ + if (!hci_req_is_complete(hdev)) + return; + + if (hdev->sent_cmd) + req_complete = bt_cb(hdev->sent_cmd)->req.complete; + + /* If the request doesn't have a complete callback or there + * are other commands/requests in the hdev queue we consider + * this request as completed. + */ + if (!req_complete || !skb_queue_empty(&hdev->cmd_q)) + hci_req_cmd_complete(hdev, opcode, status); +} + static void hci_rx_work(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, rx_work); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 14e872aa0d2c..8b878a3bdf69 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -53,6 +53,7 @@ static void hci_cc_inquiry_cancel(struct hci_dev *hdev, struct sk_buff *skb) hci_discovery_set_state(hdev, DISCOVERY_STOPPED); hci_dev_unlock(hdev); + hci_req_cmd_complete(hdev, HCI_OP_INQUIRY, status); hci_req_complete(hdev, HCI_OP_INQUIRY_CANCEL, status); hci_conn_check_pending(hdev); @@ -1692,6 +1693,7 @@ static void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%2.2x", hdev->name, status); + hci_req_cmd_complete(hdev, HCI_OP_INQUIRY, status); hci_req_complete(hdev, HCI_OP_INQUIRY, status); hci_conn_check_pending(hdev); @@ -2254,6 +2256,7 @@ static void hci_qos_setup_complete_evt(struct hci_dev *hdev, static void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_cmd_complete *ev = (void *) skb->data; + u8 status = skb->data[sizeof(*ev)]; __u16 opcode; skb_pull(skb, sizeof(*ev)); @@ -2497,6 +2500,8 @@ static void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) if (ev->opcode != HCI_OP_NOP) del_timer(&hdev->cmd_timer); + hci_req_cmd_complete(hdev, ev->opcode, status); + if (ev->ncmd && !test_bit(HCI_RESET, &hdev->flags)) { atomic_set(&hdev->cmd_cnt, 1); if (!skb_queue_empty(&hdev->cmd_q)) @@ -2590,6 +2595,8 @@ static void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb) if (ev->opcode != HCI_OP_NOP) del_timer(&hdev->cmd_timer); + hci_req_cmd_status(hdev, ev->opcode, ev->status); + if (ev->ncmd && !test_bit(HCI_RESET, &hdev->flags)) { atomic_set(&hdev->cmd_cnt, 1); if (!skb_queue_empty(&hdev->cmd_q)) -- cgit v1.2.3 From 42c6b129cd8c2aa5012a78ec39672e7052cc677a Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Tue, 5 Mar 2013 20:37:49 +0200 Subject: Bluetooth: Use async requests internally in hci_req_sync This patch converts the hci_req_sync() procedure to internaly use the asynchronous HCI requests. The hci_req_sync mechanism relies on hci_req_complete() calls from hci_event.c into hci_core.c whenever a HCI command completes. This is very similar to what asynchronous requests do and makes the conversion fairly straight forward by converting hci_req_complete into a request complete callback. By this change hci_req_complete (renamed to hci_req_sync_complete) becomes private to hci_core.c and all calls to it can be removed from hci_event.c. The commands in each hci_req_sync procedure are collected into their own request by passing the hci_request pointer to the request callback (instead of the hci_dev pointer). The one slight exception is the HCI init request which has the special handling of HCI driver specific initialization commands. These commands are run in their own request prior to the "main" init request. One other extra change that this patch must contain is the handling of spontaneous HCI reset complete events that some controllers exhibit. These were previously handled in the hci_req_complete function but the right place for them now becomes the hci_req_cmd_complete function. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 2 - net/bluetooth/hci_core.c | 271 ++++++++++++++++++++++----------------- net/bluetooth/hci_event.c | 78 +---------- 3 files changed, 156 insertions(+), 195 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index d732d6894adc..3f124f43fcb1 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1164,8 +1164,6 @@ struct hci_sec_filter { #define hci_req_lock(d) mutex_lock(&d->req_lock) #define hci_req_unlock(d) mutex_unlock(&d->req_lock) -void hci_req_complete(struct hci_dev *hdev, __u16 cmd, int result); - void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, u16 to_multiplier); void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __u8 rand[8], diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 0ada2ec36e7b..6218eced1530 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -57,36 +57,9 @@ static void hci_notify(struct hci_dev *hdev, int event) /* ---- HCI requests ---- */ -void hci_req_complete(struct hci_dev *hdev, __u16 cmd, int result) +static void hci_req_sync_complete(struct hci_dev *hdev, u8 result) { - BT_DBG("%s command 0x%4.4x result 0x%2.2x", hdev->name, cmd, result); - - /* If this is the init phase check if the completed command matches - * the last init command, and if not just return. - */ - if (test_bit(HCI_INIT, &hdev->flags) && hdev->init_last_cmd != cmd) { - struct hci_command_hdr *sent = (void *) hdev->sent_cmd->data; - u16 opcode = __le16_to_cpu(sent->opcode); - struct sk_buff *skb; - - /* Some CSR based controllers generate a spontaneous - * reset complete event during init and any pending - * command will never be completed. In such a case we - * need to resend whatever was the last sent - * command. - */ - - if (cmd != HCI_OP_RESET || opcode == HCI_OP_RESET) - return; - - skb = skb_clone(hdev->sent_cmd, GFP_ATOMIC); - if (skb) { - skb_queue_head(&hdev->cmd_q, skb); - queue_work(hdev->workqueue, &hdev->cmd_work); - } - - return; - } + BT_DBG("%s result 0x%2.2x", hdev->name, result); if (hdev->req_status == HCI_REQ_PEND) { hdev->req_result = result; @@ -108,26 +81,36 @@ static void hci_req_cancel(struct hci_dev *hdev, int err) /* Execute request and wait for completion. */ static int __hci_req_sync(struct hci_dev *hdev, - void (*req)(struct hci_dev *hdev, unsigned long opt), + void (*func)(struct hci_request *req, + unsigned long opt), unsigned long opt, __u32 timeout) { + struct hci_request req; DECLARE_WAITQUEUE(wait, current); int err = 0; BT_DBG("%s start", hdev->name); + hci_req_init(&req, hdev); + hdev->req_status = HCI_REQ_PEND; add_wait_queue(&hdev->req_wait_q, &wait); set_current_state(TASK_INTERRUPTIBLE); - req(hdev, opt); + func(&req, opt); - /* If the request didn't send any commands return immediately */ - if (skb_queue_empty(&hdev->cmd_q) && atomic_read(&hdev->cmd_cnt)) { + err = hci_req_run(&req, hci_req_sync_complete); + if (err < 0) { hdev->req_status = 0; remove_wait_queue(&hdev->req_wait_q, &wait); - return err; + /* req_run will fail if the request did not add any + * commands to the queue, something that can happen when + * a request with conditionals doesn't trigger any + * commands to be sent. This is normal behavior and + * should not trigger an error return. + */ + return 0; } schedule_timeout(timeout); @@ -159,7 +142,8 @@ static int __hci_req_sync(struct hci_dev *hdev, } static int hci_req_sync(struct hci_dev *hdev, - void (*req)(struct hci_dev *hdev, unsigned long opt), + void (*req)(struct hci_request *req, + unsigned long opt), unsigned long opt, __u32 timeout) { int ret; @@ -175,72 +159,80 @@ static int hci_req_sync(struct hci_dev *hdev, return ret; } -static void hci_reset_req(struct hci_dev *hdev, unsigned long opt) +static void hci_reset_req(struct hci_request *req, unsigned long opt) { - BT_DBG("%s %ld", hdev->name, opt); + BT_DBG("%s %ld", req->hdev->name, opt); /* Reset device */ - set_bit(HCI_RESET, &hdev->flags); - hci_send_cmd(hdev, HCI_OP_RESET, 0, NULL); + set_bit(HCI_RESET, &req->hdev->flags); + hci_req_add(req, HCI_OP_RESET, 0, NULL); } -static void bredr_init(struct hci_dev *hdev) +static void bredr_init(struct hci_request *req) { - hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_PACKET_BASED; + req->hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_PACKET_BASED; /* Read Local Supported Features */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_FEATURES, 0, NULL); + hci_req_add(req, HCI_OP_READ_LOCAL_FEATURES, 0, NULL); /* Read Local Version */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL); + hci_req_add(req, HCI_OP_READ_LOCAL_VERSION, 0, NULL); /* Read BD Address */ - hci_send_cmd(hdev, HCI_OP_READ_BD_ADDR, 0, NULL); + hci_req_add(req, HCI_OP_READ_BD_ADDR, 0, NULL); } -static void amp_init(struct hci_dev *hdev) +static void amp_init(struct hci_request *req) { - hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_BLOCK_BASED; + req->hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_BLOCK_BASED; /* Read Local Version */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL); + hci_req_add(req, HCI_OP_READ_LOCAL_VERSION, 0, NULL); /* Read Local AMP Info */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_AMP_INFO, 0, NULL); + hci_req_add(req, HCI_OP_READ_LOCAL_AMP_INFO, 0, NULL); /* Read Data Blk size */ - hci_send_cmd(hdev, HCI_OP_READ_DATA_BLOCK_SIZE, 0, NULL); + hci_req_add(req, HCI_OP_READ_DATA_BLOCK_SIZE, 0, NULL); } -static void hci_init1_req(struct hci_dev *hdev, unsigned long opt) +static void hci_init1_req(struct hci_request *req, unsigned long opt) { + struct hci_dev *hdev = req->hdev; + struct hci_request init_req; struct sk_buff *skb; BT_DBG("%s %ld", hdev->name, opt); /* Driver initialization */ + hci_req_init(&init_req, hdev); + /* Special commands */ while ((skb = skb_dequeue(&hdev->driver_init))) { bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; skb->dev = (void *) hdev; - skb_queue_tail(&hdev->cmd_q, skb); - queue_work(hdev->workqueue, &hdev->cmd_work); + if (skb_queue_empty(&init_req.cmd_q)) + bt_cb(skb)->req.start = true; + + skb_queue_tail(&init_req.cmd_q, skb); } skb_queue_purge(&hdev->driver_init); + hci_req_run(&init_req, NULL); + /* Reset */ if (!test_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks)) - hci_reset_req(hdev, 0); + hci_reset_req(req, 0); switch (hdev->dev_type) { case HCI_BREDR: - bredr_init(hdev); + bredr_init(req); break; case HCI_AMP: - amp_init(hdev); + amp_init(req); break; default: @@ -249,53 +241,53 @@ static void hci_init1_req(struct hci_dev *hdev, unsigned long opt) } } -static void bredr_setup(struct hci_dev *hdev) +static void bredr_setup(struct hci_request *req) { struct hci_cp_delete_stored_link_key cp; __le16 param; __u8 flt_type; /* Read Buffer Size (ACL mtu, max pkt, etc.) */ - hci_send_cmd(hdev, HCI_OP_READ_BUFFER_SIZE, 0, NULL); + hci_req_add(req, HCI_OP_READ_BUFFER_SIZE, 0, NULL); /* Read Class of Device */ - hci_send_cmd(hdev, HCI_OP_READ_CLASS_OF_DEV, 0, NULL); + hci_req_add(req, HCI_OP_READ_CLASS_OF_DEV, 0, NULL); /* Read Local Name */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_NAME, 0, NULL); + hci_req_add(req, HCI_OP_READ_LOCAL_NAME, 0, NULL); /* Read Voice Setting */ - hci_send_cmd(hdev, HCI_OP_READ_VOICE_SETTING, 0, NULL); + hci_req_add(req, HCI_OP_READ_VOICE_SETTING, 0, NULL); /* Clear Event Filters */ flt_type = HCI_FLT_CLEAR_ALL; - hci_send_cmd(hdev, HCI_OP_SET_EVENT_FLT, 1, &flt_type); + hci_req_add(req, HCI_OP_SET_EVENT_FLT, 1, &flt_type); /* Connection accept timeout ~20 secs */ param = __constant_cpu_to_le16(0x7d00); - hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); + hci_req_add(req, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); bacpy(&cp.bdaddr, BDADDR_ANY); cp.delete_all = 0x01; - hci_send_cmd(hdev, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp); + hci_req_add(req, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp); } -static void le_setup(struct hci_dev *hdev) +static void le_setup(struct hci_request *req) { /* Read LE Buffer Size */ - hci_send_cmd(hdev, HCI_OP_LE_READ_BUFFER_SIZE, 0, NULL); + hci_req_add(req, HCI_OP_LE_READ_BUFFER_SIZE, 0, NULL); /* Read LE Local Supported Features */ - hci_send_cmd(hdev, HCI_OP_LE_READ_LOCAL_FEATURES, 0, NULL); + hci_req_add(req, HCI_OP_LE_READ_LOCAL_FEATURES, 0, NULL); /* Read LE Advertising Channel TX Power */ - hci_send_cmd(hdev, HCI_OP_LE_READ_ADV_TX_POWER, 0, NULL); + hci_req_add(req, HCI_OP_LE_READ_ADV_TX_POWER, 0, NULL); /* Read LE White List Size */ - hci_send_cmd(hdev, HCI_OP_LE_READ_WHITE_LIST_SIZE, 0, NULL); + hci_req_add(req, HCI_OP_LE_READ_WHITE_LIST_SIZE, 0, NULL); /* Read LE Supported States */ - hci_send_cmd(hdev, HCI_OP_LE_READ_SUPPORTED_STATES, 0, NULL); + hci_req_add(req, HCI_OP_LE_READ_SUPPORTED_STATES, 0, NULL); } static u8 hci_get_inquiry_mode(struct hci_dev *hdev) @@ -326,17 +318,19 @@ static u8 hci_get_inquiry_mode(struct hci_dev *hdev) return 0x00; } -static void hci_setup_inquiry_mode(struct hci_dev *hdev) +static void hci_setup_inquiry_mode(struct hci_request *req) { u8 mode; - mode = hci_get_inquiry_mode(hdev); + mode = hci_get_inquiry_mode(req->hdev); - hci_send_cmd(hdev, HCI_OP_WRITE_INQUIRY_MODE, 1, &mode); + hci_req_add(req, HCI_OP_WRITE_INQUIRY_MODE, 1, &mode); } -static void hci_setup_event_mask(struct hci_dev *hdev) +static void hci_setup_event_mask(struct hci_request *req) { + struct hci_dev *hdev = req->hdev; + /* The second byte is 0xff instead of 0x9f (two reserved bits * disabled) since a Broadcom 1.2 dongle doesn't respond to the * command otherwise. @@ -392,67 +386,70 @@ static void hci_setup_event_mask(struct hci_dev *hdev) if (lmp_le_capable(hdev)) events[7] |= 0x20; /* LE Meta-Event */ - hci_send_cmd(hdev, HCI_OP_SET_EVENT_MASK, sizeof(events), events); + hci_req_add(req, HCI_OP_SET_EVENT_MASK, sizeof(events), events); if (lmp_le_capable(hdev)) { memset(events, 0, sizeof(events)); events[0] = 0x1f; - hci_send_cmd(hdev, HCI_OP_LE_SET_EVENT_MASK, - sizeof(events), events); + hci_req_add(req, HCI_OP_LE_SET_EVENT_MASK, + sizeof(events), events); } } -static void hci_init2_req(struct hci_dev *hdev, unsigned long opt) +static void hci_init2_req(struct hci_request *req, unsigned long opt) { + struct hci_dev *hdev = req->hdev; + if (lmp_bredr_capable(hdev)) - bredr_setup(hdev); + bredr_setup(req); if (lmp_le_capable(hdev)) - le_setup(hdev); + le_setup(req); - hci_setup_event_mask(hdev); + hci_setup_event_mask(req); if (hdev->hci_ver > BLUETOOTH_VER_1_1) - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_COMMANDS, 0, NULL); + hci_req_add(req, HCI_OP_READ_LOCAL_COMMANDS, 0, NULL); if (lmp_ssp_capable(hdev)) { if (test_bit(HCI_SSP_ENABLED, &hdev->dev_flags)) { u8 mode = 0x01; - hci_send_cmd(hdev, HCI_OP_WRITE_SSP_MODE, - sizeof(mode), &mode); + hci_req_add(req, HCI_OP_WRITE_SSP_MODE, + sizeof(mode), &mode); } else { struct hci_cp_write_eir cp; memset(hdev->eir, 0, sizeof(hdev->eir)); memset(&cp, 0, sizeof(cp)); - hci_send_cmd(hdev, HCI_OP_WRITE_EIR, sizeof(cp), &cp); + hci_req_add(req, HCI_OP_WRITE_EIR, sizeof(cp), &cp); } } if (lmp_inq_rssi_capable(hdev)) - hci_setup_inquiry_mode(hdev); + hci_setup_inquiry_mode(req); if (lmp_inq_tx_pwr_capable(hdev)) - hci_send_cmd(hdev, HCI_OP_READ_INQ_RSP_TX_POWER, 0, NULL); + hci_req_add(req, HCI_OP_READ_INQ_RSP_TX_POWER, 0, NULL); if (lmp_ext_feat_capable(hdev)) { struct hci_cp_read_local_ext_features cp; cp.page = 0x01; - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_EXT_FEATURES, sizeof(cp), - &cp); + hci_req_add(req, HCI_OP_READ_LOCAL_EXT_FEATURES, + sizeof(cp), &cp); } if (test_bit(HCI_LINK_SECURITY, &hdev->dev_flags)) { u8 enable = 1; - hci_send_cmd(hdev, HCI_OP_WRITE_AUTH_ENABLE, sizeof(enable), - &enable); + hci_req_add(req, HCI_OP_WRITE_AUTH_ENABLE, sizeof(enable), + &enable); } } -static void hci_setup_link_policy(struct hci_dev *hdev) +static void hci_setup_link_policy(struct hci_request *req) { + struct hci_dev *hdev = req->hdev; struct hci_cp_write_def_link_policy cp; u16 link_policy = 0; @@ -466,11 +463,12 @@ static void hci_setup_link_policy(struct hci_dev *hdev) link_policy |= HCI_LP_PARK; cp.policy = cpu_to_le16(link_policy); - hci_send_cmd(hdev, HCI_OP_WRITE_DEF_LINK_POLICY, sizeof(cp), &cp); + hci_req_add(req, HCI_OP_WRITE_DEF_LINK_POLICY, sizeof(cp), &cp); } -static void hci_set_le_support(struct hci_dev *hdev) +static void hci_set_le_support(struct hci_request *req) { + struct hci_dev *hdev = req->hdev; struct hci_cp_write_le_host_supported cp; memset(&cp, 0, sizeof(cp)); @@ -481,17 +479,19 @@ static void hci_set_le_support(struct hci_dev *hdev) } if (cp.le != lmp_host_le_capable(hdev)) - hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(cp), - &cp); + hci_req_add(req, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(cp), + &cp); } -static void hci_init3_req(struct hci_dev *hdev, unsigned long opt) +static void hci_init3_req(struct hci_request *req, unsigned long opt) { + struct hci_dev *hdev = req->hdev; + if (hdev->commands[5] & 0x10) - hci_setup_link_policy(hdev); + hci_setup_link_policy(req); if (lmp_le_capable(hdev)) - hci_set_le_support(hdev); + hci_set_le_support(req); } static int __hci_init(struct hci_dev *hdev) @@ -516,44 +516,44 @@ static int __hci_init(struct hci_dev *hdev) return __hci_req_sync(hdev, hci_init3_req, 0, HCI_INIT_TIMEOUT); } -static void hci_scan_req(struct hci_dev *hdev, unsigned long opt) +static void hci_scan_req(struct hci_request *req, unsigned long opt) { __u8 scan = opt; - BT_DBG("%s %x", hdev->name, scan); + BT_DBG("%s %x", req->hdev->name, scan); /* Inquiry and Page scans */ - hci_send_cmd(hdev, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); + hci_req_add(req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); } -static void hci_auth_req(struct hci_dev *hdev, unsigned long opt) +static void hci_auth_req(struct hci_request *req, unsigned long opt) { __u8 auth = opt; - BT_DBG("%s %x", hdev->name, auth); + BT_DBG("%s %x", req->hdev->name, auth); /* Authentication */ - hci_send_cmd(hdev, HCI_OP_WRITE_AUTH_ENABLE, 1, &auth); + hci_req_add(req, HCI_OP_WRITE_AUTH_ENABLE, 1, &auth); } -static void hci_encrypt_req(struct hci_dev *hdev, unsigned long opt) +static void hci_encrypt_req(struct hci_request *req, unsigned long opt) { __u8 encrypt = opt; - BT_DBG("%s %x", hdev->name, encrypt); + BT_DBG("%s %x", req->hdev->name, encrypt); /* Encryption */ - hci_send_cmd(hdev, HCI_OP_WRITE_ENCRYPT_MODE, 1, &encrypt); + hci_req_add(req, HCI_OP_WRITE_ENCRYPT_MODE, 1, &encrypt); } -static void hci_linkpol_req(struct hci_dev *hdev, unsigned long opt) +static void hci_linkpol_req(struct hci_request *req, unsigned long opt) { __le16 policy = cpu_to_le16(opt); - BT_DBG("%s %x", hdev->name, policy); + BT_DBG("%s %x", req->hdev->name, policy); /* Default link policy */ - hci_send_cmd(hdev, HCI_OP_WRITE_DEF_LINK_POLICY, 2, &policy); + hci_req_add(req, HCI_OP_WRITE_DEF_LINK_POLICY, 2, &policy); } /* Get HCI device by index. @@ -790,9 +790,10 @@ static int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf) return copied; } -static void hci_inq_req(struct hci_dev *hdev, unsigned long opt) +static void hci_inq_req(struct hci_request *req, unsigned long opt) { struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt; + struct hci_dev *hdev = req->hdev; struct hci_cp_inquiry cp; BT_DBG("%s", hdev->name); @@ -804,7 +805,7 @@ static void hci_inq_req(struct hci_dev *hdev, unsigned long opt) memcpy(&cp.lap, &ir->lap, 3); cp.length = ir->length; cp.num_rsp = ir->num_rsp; - hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp); + hci_req_add(req, HCI_OP_INQUIRY, sizeof(cp), &cp); } int hci_inquiry(void __user *arg) @@ -1845,7 +1846,7 @@ int hci_blacklist_del(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) return mgmt_device_unblocked(hdev, bdaddr, type); } -static void le_scan_param_req(struct hci_dev *hdev, unsigned long opt) +static void le_scan_param_req(struct hci_request *req, unsigned long opt) { struct le_scan_params *param = (struct le_scan_params *) opt; struct hci_cp_le_set_scan_param cp; @@ -1855,10 +1856,10 @@ static void le_scan_param_req(struct hci_dev *hdev, unsigned long opt) cp.interval = cpu_to_le16(param->interval); cp.window = cpu_to_le16(param->window); - hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_PARAM, sizeof(cp), &cp); + hci_req_add(req, HCI_OP_LE_SET_SCAN_PARAM, sizeof(cp), &cp); } -static void le_scan_enable_req(struct hci_dev *hdev, unsigned long opt) +static void le_scan_enable_req(struct hci_request *req, unsigned long opt) { struct hci_cp_le_set_scan_enable cp; @@ -1866,7 +1867,7 @@ static void le_scan_enable_req(struct hci_dev *hdev, unsigned long opt) cp.enable = 1; cp.filter_dup = 1; - hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(cp), &cp); + hci_req_add(req, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(cp), &cp); } static int hci_do_le_scan(struct hci_dev *hdev, u8 type, u16 interval, @@ -3219,6 +3220,28 @@ static bool hci_req_is_complete(struct hci_dev *hdev) return bt_cb(skb)->req.start; } +static void hci_resend_last(struct hci_dev *hdev) +{ + struct hci_command_hdr *sent; + struct sk_buff *skb; + u16 opcode; + + if (!hdev->sent_cmd) + return; + + sent = (void *) hdev->sent_cmd->data; + opcode = __le16_to_cpu(sent->opcode); + if (opcode == HCI_OP_RESET) + return; + + skb = skb_clone(hdev->sent_cmd, GFP_KERNEL); + if (!skb) + return; + + skb_queue_head(&hdev->cmd_q, skb); + queue_work(hdev->workqueue, &hdev->cmd_work); +} + void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status) { hci_req_complete_t req_complete = NULL; @@ -3227,11 +3250,21 @@ void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status) BT_DBG("opcode 0x%04x status 0x%02x", opcode, status); - /* Check that the completed command really matches the last one - * that was sent. + /* If the completed command doesn't match the last one that was + * sent we need to do special handling of it. */ - if (!hci_sent_cmd_data(hdev, opcode)) + if (!hci_sent_cmd_data(hdev, opcode)) { + /* Some CSR based controllers generate a spontaneous + * reset complete event during init and any pending + * command will never be completed. In such a case we + * need to resend whatever was the last sent + * command. + */ + if (test_bit(HCI_INIT, &hdev->flags) && opcode == HCI_OP_RESET) + hci_resend_last(hdev); + return; + } /* If the command succeeded and there's still more commands in * this request the request is not yet complete. diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 8b878a3bdf69..0dd85a0c05f4 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -54,7 +54,6 @@ static void hci_cc_inquiry_cancel(struct hci_dev *hdev, struct sk_buff *skb) hci_dev_unlock(hdev); hci_req_cmd_complete(hdev, HCI_OP_INQUIRY, status); - hci_req_complete(hdev, HCI_OP_INQUIRY_CANCEL, status); hci_conn_check_pending(hdev); } @@ -184,8 +183,6 @@ static void hci_cc_write_def_link_policy(struct hci_dev *hdev, if (!status) hdev->link_policy = get_unaligned_le16(sent); - - hci_req_complete(hdev, HCI_OP_WRITE_DEF_LINK_POLICY, status); } static void hci_cc_reset(struct hci_dev *hdev, struct sk_buff *skb) @@ -196,8 +193,6 @@ static void hci_cc_reset(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_RESET, &hdev->flags); - hci_req_complete(hdev, HCI_OP_RESET, status); - /* Reset all non-persistent flags */ hdev->dev_flags &= ~(BIT(HCI_LE_SCAN) | BIT(HCI_PENDING_CLASS) | BIT(HCI_PERIODIC_INQ)); @@ -232,8 +227,6 @@ static void hci_cc_write_local_name(struct hci_dev *hdev, struct sk_buff *skb) if (!status && !test_bit(HCI_INIT, &hdev->flags)) hci_update_ad(hdev); - - hci_req_complete(hdev, HCI_OP_WRITE_LOCAL_NAME, status); } static void hci_cc_read_local_name(struct hci_dev *hdev, struct sk_buff *skb) @@ -271,8 +264,6 @@ static void hci_cc_write_auth_enable(struct hci_dev *hdev, struct sk_buff *skb) if (test_bit(HCI_MGMT, &hdev->dev_flags)) mgmt_auth_enable_complete(hdev, status); - - hci_req_complete(hdev, HCI_OP_WRITE_AUTH_ENABLE, status); } static void hci_cc_write_encrypt_mode(struct hci_dev *hdev, struct sk_buff *skb) @@ -294,8 +285,6 @@ static void hci_cc_write_encrypt_mode(struct hci_dev *hdev, struct sk_buff *skb) else clear_bit(HCI_ENCRYPT, &hdev->flags); } - - hci_req_complete(hdev, HCI_OP_WRITE_ENCRYPT_MODE, status); } static void hci_cc_write_scan_enable(struct hci_dev *hdev, struct sk_buff *skb) @@ -344,7 +333,6 @@ static void hci_cc_write_scan_enable(struct hci_dev *hdev, struct sk_buff *skb) done: hci_dev_unlock(hdev); - hci_req_complete(hdev, HCI_OP_WRITE_SCAN_ENABLE, status); } static void hci_cc_read_class_of_dev(struct hci_dev *hdev, struct sk_buff *skb) @@ -441,8 +429,6 @@ static void hci_cc_host_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_HOST_BUFFER_SIZE, status); } static void hci_cc_write_ssp_mode(struct hci_dev *hdev, struct sk_buff *skb) @@ -480,7 +466,7 @@ static void hci_cc_read_local_version(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); if (rp->status) - goto done; + return; hdev->hci_ver = rp->hci_ver; hdev->hci_rev = __le16_to_cpu(rp->hci_rev); @@ -490,9 +476,6 @@ static void hci_cc_read_local_version(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s manufacturer 0x%4.4x hci ver %d:%d", hdev->name, hdev->manufacturer, hdev->hci_ver, hdev->hci_rev); - -done: - hci_req_complete(hdev, HCI_OP_READ_LOCAL_VERSION, rp->status); } static void hci_cc_read_local_commands(struct hci_dev *hdev, @@ -504,8 +487,6 @@ static void hci_cc_read_local_commands(struct hci_dev *hdev, if (!rp->status) memcpy(hdev->commands, rp->commands, sizeof(hdev->commands)); - - hci_req_complete(hdev, HCI_OP_READ_LOCAL_COMMANDS, rp->status); } static void hci_cc_read_local_features(struct hci_dev *hdev, @@ -572,7 +553,7 @@ static void hci_cc_read_local_ext_features(struct hci_dev *hdev, BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); if (rp->status) - goto done; + return; switch (rp->page) { case 0: @@ -582,9 +563,6 @@ static void hci_cc_read_local_ext_features(struct hci_dev *hdev, memcpy(hdev->host_features, rp->features, 8); break; } - -done: - hci_req_complete(hdev, HCI_OP_READ_LOCAL_EXT_FEATURES, rp->status); } static void hci_cc_read_flow_control_mode(struct hci_dev *hdev, @@ -594,12 +572,8 @@ static void hci_cc_read_flow_control_mode(struct hci_dev *hdev, BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); - if (rp->status) - return; - - hdev->flow_ctl_mode = rp->mode; - - hci_req_complete(hdev, HCI_OP_READ_FLOW_CONTROL_MODE, rp->status); + if (!rp->status) + hdev->flow_ctl_mode = rp->mode; } static void hci_cc_read_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) @@ -636,8 +610,6 @@ static void hci_cc_read_bd_addr(struct hci_dev *hdev, struct sk_buff *skb) if (!rp->status) bacpy(&hdev->bdaddr, &rp->bdaddr); - - hci_req_complete(hdev, HCI_OP_READ_BD_ADDR, rp->status); } static void hci_cc_read_data_block_size(struct hci_dev *hdev, @@ -658,8 +630,6 @@ static void hci_cc_read_data_block_size(struct hci_dev *hdev, BT_DBG("%s blk mtu %d cnt %d len %d", hdev->name, hdev->block_mtu, hdev->block_cnt, hdev->block_len); - - hci_req_complete(hdev, HCI_OP_READ_DATA_BLOCK_SIZE, rp->status); } static void hci_cc_write_ca_timeout(struct hci_dev *hdev, struct sk_buff *skb) @@ -667,8 +637,6 @@ static void hci_cc_write_ca_timeout(struct hci_dev *hdev, struct sk_buff *skb) __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_WRITE_CA_TIMEOUT, status); } static void hci_cc_read_local_amp_info(struct hci_dev *hdev, @@ -692,8 +660,6 @@ static void hci_cc_read_local_amp_info(struct hci_dev *hdev, hdev->amp_be_flush_to = __le32_to_cpu(rp->be_flush_to); hdev->amp_max_flush_to = __le32_to_cpu(rp->max_flush_to); - hci_req_complete(hdev, HCI_OP_READ_LOCAL_AMP_INFO, rp->status); - a2mp_rsp: a2mp_send_getinfo_rsp(hdev); } @@ -741,8 +707,6 @@ static void hci_cc_delete_stored_link_key(struct hci_dev *hdev, __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_DELETE_STORED_LINK_KEY, status); } static void hci_cc_set_event_mask(struct hci_dev *hdev, struct sk_buff *skb) @@ -750,8 +714,6 @@ static void hci_cc_set_event_mask(struct hci_dev *hdev, struct sk_buff *skb) __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_SET_EVENT_MASK, status); } static void hci_cc_write_inquiry_mode(struct hci_dev *hdev, @@ -760,8 +722,6 @@ static void hci_cc_write_inquiry_mode(struct hci_dev *hdev, __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_WRITE_INQUIRY_MODE, status); } static void hci_cc_read_inq_rsp_tx_power(struct hci_dev *hdev, @@ -773,8 +733,6 @@ static void hci_cc_read_inq_rsp_tx_power(struct hci_dev *hdev, if (!rp->status) hdev->inq_tx_power = rp->tx_power; - - hci_req_complete(hdev, HCI_OP_READ_INQ_RSP_TX_POWER, rp->status); } static void hci_cc_set_event_flt(struct hci_dev *hdev, struct sk_buff *skb) @@ -782,8 +740,6 @@ static void hci_cc_set_event_flt(struct hci_dev *hdev, struct sk_buff *skb) __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_SET_EVENT_FLT, status); } static void hci_cc_pin_code_reply(struct hci_dev *hdev, struct sk_buff *skb) @@ -845,8 +801,6 @@ static void hci_cc_le_read_buffer_size(struct hci_dev *hdev, hdev->le_cnt = hdev->le_pkts; BT_DBG("%s le mtu %d:%d", hdev->name, hdev->le_mtu, hdev->le_pkts); - - hci_req_complete(hdev, HCI_OP_LE_READ_BUFFER_SIZE, rp->status); } static void hci_cc_le_read_local_features(struct hci_dev *hdev, @@ -858,8 +812,6 @@ static void hci_cc_le_read_local_features(struct hci_dev *hdev, if (!rp->status) memcpy(hdev->le_features, rp->features, 8); - - hci_req_complete(hdev, HCI_OP_LE_READ_LOCAL_FEATURES, rp->status); } static void hci_cc_le_read_adv_tx_power(struct hci_dev *hdev, @@ -874,8 +826,6 @@ static void hci_cc_le_read_adv_tx_power(struct hci_dev *hdev, if (!test_bit(HCI_INIT, &hdev->flags)) hci_update_ad(hdev); } - - hci_req_complete(hdev, HCI_OP_LE_READ_ADV_TX_POWER, rp->status); } static void hci_cc_le_set_event_mask(struct hci_dev *hdev, struct sk_buff *skb) @@ -883,8 +833,6 @@ static void hci_cc_le_set_event_mask(struct hci_dev *hdev, struct sk_buff *skb) __u8 status = *((__u8 *) skb->data); BT_DBG("%s status 0x%2.2x", hdev->name, status); - - hci_req_complete(hdev, HCI_OP_LE_SET_EVENT_MASK, status); } static void hci_cc_user_confirm_reply(struct hci_dev *hdev, struct sk_buff *skb) @@ -985,8 +933,6 @@ static void hci_cc_le_set_adv_enable(struct hci_dev *hdev, struct sk_buff *skb) if (!test_bit(HCI_INIT, &hdev->flags)) hci_update_ad(hdev); - - hci_req_complete(hdev, HCI_OP_LE_SET_ADV_ENABLE, status); } static void hci_cc_le_set_scan_param(struct hci_dev *hdev, struct sk_buff *skb) @@ -995,8 +941,6 @@ static void hci_cc_le_set_scan_param(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%2.2x", hdev->name, status); - hci_req_complete(hdev, HCI_OP_LE_SET_SCAN_PARAM, status); - if (status) { hci_dev_lock(hdev); mgmt_start_discovery_failed(hdev, status); @@ -1019,8 +963,6 @@ static void hci_cc_le_set_scan_enable(struct hci_dev *hdev, switch (cp->enable) { case LE_SCANNING_ENABLED: - hci_req_complete(hdev, HCI_OP_LE_SET_SCAN_ENABLE, status); - if (status) { hci_dev_lock(hdev); mgmt_start_discovery_failed(hdev, status); @@ -1071,8 +1013,6 @@ static void hci_cc_le_read_white_list_size(struct hci_dev *hdev, if (!rp->status) hdev->le_white_list_size = rp->size; - - hci_req_complete(hdev, HCI_OP_LE_READ_WHITE_LIST_SIZE, rp->status); } static void hci_cc_le_ltk_reply(struct hci_dev *hdev, struct sk_buff *skb) @@ -1083,8 +1023,6 @@ static void hci_cc_le_ltk_reply(struct hci_dev *hdev, struct sk_buff *skb) if (rp->status) return; - - hci_req_complete(hdev, HCI_OP_LE_LTK_REPLY, rp->status); } static void hci_cc_le_ltk_neg_reply(struct hci_dev *hdev, struct sk_buff *skb) @@ -1095,8 +1033,6 @@ static void hci_cc_le_ltk_neg_reply(struct hci_dev *hdev, struct sk_buff *skb) if (rp->status) return; - - hci_req_complete(hdev, HCI_OP_LE_LTK_NEG_REPLY, rp->status); } static void hci_cc_le_read_supported_states(struct hci_dev *hdev, @@ -1108,8 +1044,6 @@ static void hci_cc_le_read_supported_states(struct hci_dev *hdev, if (!rp->status) memcpy(hdev->le_states, rp->le_states, 8); - - hci_req_complete(hdev, HCI_OP_LE_READ_SUPPORTED_STATES, rp->status); } static void hci_cc_write_le_host_supported(struct hci_dev *hdev, @@ -1139,8 +1073,6 @@ static void hci_cc_write_le_host_supported(struct hci_dev *hdev, if (test_bit(HCI_MGMT, &hdev->dev_flags) && !test_bit(HCI_INIT, &hdev->flags)) mgmt_le_enable_complete(hdev, sent->le, status); - - hci_req_complete(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, status); } static void hci_cc_write_remote_amp_assoc(struct hci_dev *hdev, @@ -1162,7 +1094,6 @@ static void hci_cs_inquiry(struct hci_dev *hdev, __u8 status) BT_DBG("%s status 0x%2.2x", hdev->name, status); if (status) { - hci_req_complete(hdev, HCI_OP_INQUIRY, status); hci_conn_check_pending(hdev); hci_dev_lock(hdev); if (test_bit(HCI_MGMT, &hdev->dev_flags)) @@ -1694,7 +1625,6 @@ static void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) BT_DBG("%s status 0x%2.2x", hdev->name, status); hci_req_cmd_complete(hdev, HCI_OP_INQUIRY, status); - hci_req_complete(hdev, HCI_OP_INQUIRY, status); hci_conn_check_pending(hdev); -- cgit v1.2.3 From cecbb967b2f5c52e090978ff6afe7deddbfbeda5 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Tue, 5 Mar 2013 20:37:50 +0200 Subject: Bluetooth: Remove unused hdev->init_last_cmd This variable is no longer needed (due to async HCI request support and the conversion of hci_req_sync to use it), so it can be safely removed. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 2 -- net/bluetooth/hci_core.c | 6 ------ 2 files changed, 8 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 3f124f43fcb1..3a9cbf2b55c0 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -248,8 +248,6 @@ struct hci_dev { __u32 req_status; __u32 req_result; - __u16 init_last_cmd; - struct list_head mgmt_pending; struct discovery_state discovery; diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 6218eced1530..3fc699db8fb5 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1015,10 +1015,7 @@ int hci_dev_open(__u16 dev) if (!test_bit(HCI_RAW, &hdev->flags)) { atomic_set(&hdev->cmd_cnt, 1); set_bit(HCI_INIT, &hdev->flags); - hdev->init_last_cmd = 0; - ret = __hci_init(hdev); - clear_bit(HCI_INIT, &hdev->flags); } @@ -2509,9 +2506,6 @@ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) return -ENOMEM; } - if (test_bit(HCI_INIT, &hdev->flags)) - hdev->init_last_cmd = opcode; - /* Stand-alone HCI commands must be flaged as * single-command requests. */ -- cgit v1.2.3 From 5d73e0342fd9bf500583868906325d42c4d2bf6f Mon Sep 17 00:00:00 2001 From: Andre Guedes Date: Fri, 8 Mar 2013 11:20:16 -0300 Subject: Bluetooth: HCI request error handling When we are building a HCI request with more than one HCI command and one of the hci_req_add calls fail, we should have some cleanup routine so the HCI commands already queued on HCI request can be deleted. Otherwise, we will face some memory leaks issues. This patch implements the HCI request error handling which is the following: If a hci_req_add fails, we save the error code in hci_ request. Once hci_req_run is called, we verify the error field. If it is different from zero, we delete all HCI commands already queued and return the error code. Signed-off-by: Andre Guedes Acked-by: Johan Hedberg Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 5 +++++ net/bluetooth/hci_core.c | 13 ++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 3a9cbf2b55c0..332ee5099a52 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1042,6 +1042,11 @@ int hci_unregister_cb(struct hci_cb *hcb); struct hci_request { struct hci_dev *hdev; struct sk_buff_head cmd_q; + + /* If something goes wrong when building the HCI request, the error + * value is stored in this field. + */ + int err; }; void hci_req_init(struct hci_request *req, struct hci_dev *hdev); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 4603464b91e2..b432baafdf12 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2443,6 +2443,7 @@ void hci_req_init(struct hci_request *req, struct hci_dev *hdev) { skb_queue_head_init(&req->cmd_q); req->hdev = hdev; + req->err = 0; } int hci_req_run(struct hci_request *req, hci_req_complete_t complete) @@ -2453,6 +2454,14 @@ int hci_req_run(struct hci_request *req, hci_req_complete_t complete) BT_DBG("length %u", skb_queue_len(&req->cmd_q)); + /* If an error occured during request building, remove all HCI + * commands queued on the HCI request queue. + */ + if (req->err) { + skb_queue_purge(&req->cmd_q); + return req->err; + } + /* Do not allow empty requests */ if (skb_queue_empty(&req->cmd_q)) return -ENODATA; @@ -2529,7 +2538,9 @@ int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param) skb = hci_prepare_cmd(hdev, opcode, plen, param); if (!skb) { - BT_ERR("%s no memory for command", hdev->name); + BT_ERR("%s no memory for command (opcode 0x%4.4x)", + hdev->name, opcode); + req->err = -ENOMEM; return -ENOMEM; } -- cgit v1.2.3 From e348fe6bbab85c513816d2536ffabac4be016442 Mon Sep 17 00:00:00 2001 From: Andre Guedes Date: Fri, 8 Mar 2013 11:20:17 -0300 Subject: Bluetooth: Make hci_req_add returning void Since no one checks the returning value of hci_req_add and HCI request errors are now handled in hci_req_run, we can make hci_ req_add returning void. Signed-off-by: Andre Guedes Acked-by: Johan Hedberg Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 2 +- net/bluetooth/hci_core.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 332ee5099a52..d6c32561fc02 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1051,7 +1051,7 @@ struct hci_request { void hci_req_init(struct hci_request *req, struct hci_dev *hdev); int hci_req_run(struct hci_request *req, hci_req_complete_t complete); -int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param); +void hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param); void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status); void hci_req_cmd_status(struct hci_dev *hdev, u16 opcode, u8 status); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index b432baafdf12..1c678757c83a 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2529,7 +2529,7 @@ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) } /* Queue a command to an asynchronous HCI request */ -int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param) +void hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param) { struct hci_dev *hdev = req->hdev; struct sk_buff *skb; @@ -2541,15 +2541,13 @@ int hci_req_add(struct hci_request *req, u16 opcode, u32 plen, void *param) BT_ERR("%s no memory for command (opcode 0x%4.4x)", hdev->name, opcode); req->err = -ENOMEM; - return -ENOMEM; + return; } if (skb_queue_empty(&req->cmd_q)) bt_cb(skb)->req.start = true; skb_queue_tail(&req->cmd_q, skb); - - return 0; } /* Get data from the previously sent command */ -- cgit v1.2.3 From 26fd76cab2e61cedc5c25f7151fb31b57ddc53c7 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 22 Feb 2013 10:53:25 +0100 Subject: NFC: llcp: Implement socket options Some LLCP services (e.g. the validation ones) require some control over the LLCP link parameters like the receive window (RW) or the MIU extension (MIUX). This can only be done through socket options. Signed-off-by: Samuel Ortiz --- include/linux/socket.h | 1 + include/uapi/linux/nfc.h | 4 ++ net/nfc/llcp/llcp.h | 3 ++ net/nfc/llcp/sock.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/socket.h b/include/linux/socket.h index 2b9f74b0ffea..428c37a1f95c 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -298,6 +298,7 @@ struct ucred { #define SOL_IUCV 277 #define SOL_CAIF 278 #define SOL_ALG 279 +#define SOL_NFC 280 /* IPX options */ #define IPX_TYPE 1 diff --git a/include/uapi/linux/nfc.h b/include/uapi/linux/nfc.h index 7969f46f1bb3..855630fe731d 100644 --- a/include/uapi/linux/nfc.h +++ b/include/uapi/linux/nfc.h @@ -220,4 +220,8 @@ struct sockaddr_nfc_llcp { #define NFC_LLCP_DIRECTION_RX 0x00 #define NFC_LLCP_DIRECTION_TX 0x01 +/* socket option names */ +#define NFC_LLCP_RW 0 +#define NFC_LLCP_MIUX 1 + #endif /*__LINUX_NFC_H */ diff --git a/net/nfc/llcp/llcp.h b/net/nfc/llcp/llcp.h index 32cec81939e6..5f117adac2e5 100644 --- a/net/nfc/llcp/llcp.h +++ b/net/nfc/llcp/llcp.h @@ -104,6 +104,9 @@ struct nfc_llcp_sock { u8 dsap; char *service_name; size_t service_name_len; + u8 rw; + u16 miux; + /* Remote link parameters */ u8 remote_rw; diff --git a/net/nfc/llcp/sock.c b/net/nfc/llcp/sock.c index cc564992ba95..9357a756f7a9 100644 --- a/net/nfc/llcp/sock.c +++ b/net/nfc/llcp/sock.c @@ -223,6 +223,121 @@ error: return ret; } +static int nfc_llcp_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, unsigned int optlen) +{ + struct sock *sk = sock->sk; + struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk); + u32 opt; + int err = 0; + + pr_debug("%p optname %d\n", sk, optname); + + if (level != SOL_NFC) + return -ENOPROTOOPT; + + lock_sock(sk); + + switch (optname) { + case NFC_LLCP_RW: + if (sk->sk_state == LLCP_CONNECTED || + sk->sk_state == LLCP_BOUND || + sk->sk_state == LLCP_LISTEN) { + err = -EINVAL; + break; + } + + if (get_user(opt, (u32 __user *) optval)) { + err = -EFAULT; + break; + } + + if (opt > LLCP_MAX_RW) { + err = -EINVAL; + break; + } + + llcp_sock->rw = (u8) opt; + + break; + + case NFC_LLCP_MIUX: + if (sk->sk_state == LLCP_CONNECTED || + sk->sk_state == LLCP_BOUND || + sk->sk_state == LLCP_LISTEN) { + err = -EINVAL; + break; + } + + if (get_user(opt, (u32 __user *) optval)) { + err = -EFAULT; + break; + } + + if (opt > LLCP_MAX_MIUX) { + err = -EINVAL; + break; + } + + llcp_sock->miux = (u16) opt; + + break; + + default: + err = -ENOPROTOOPT; + break; + } + + release_sock(sk); + + return err; +} + +static int nfc_llcp_getsockopt(struct socket *sock, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk); + int len, err = 0; + + pr_debug("%p optname %d\n", sk, optname); + + if (level != SOL_NFC) + return -ENOPROTOOPT; + + if (get_user(len, optlen)) + return -EFAULT; + + len = min_t(u32, len, sizeof(u32)); + + lock_sock(sk); + + switch (optname) { + case NFC_LLCP_RW: + if (put_user(llcp_sock->rw, (u32 __user *) optval)) + err = -EFAULT; + + break; + + case NFC_LLCP_MIUX: + if (put_user(llcp_sock->miux, (u32 __user *) optval)) + err = -EFAULT; + + break; + + default: + err = -ENOPROTOOPT; + break; + } + + release_sock(sk); + + if (put_user(len, optlen)) + return -EFAULT; + + return err; +} + void nfc_llcp_accept_unlink(struct sock *sk) { struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk); @@ -735,8 +850,8 @@ static const struct proto_ops llcp_sock_ops = { .ioctl = sock_no_ioctl, .listen = llcp_sock_listen, .shutdown = sock_no_shutdown, - .setsockopt = sock_no_setsockopt, - .getsockopt = sock_no_getsockopt, + .setsockopt = nfc_llcp_setsockopt, + .getsockopt = nfc_llcp_getsockopt, .sendmsg = llcp_sock_sendmsg, .recvmsg = llcp_sock_recvmsg, .mmap = sock_no_mmap, -- cgit v1.2.3 From d9b8d8e19b073096d3609bbd60f82148d128b555 Mon Sep 17 00:00:00 2001 From: Thierry Escande Date: Fri, 15 Feb 2013 10:43:06 +0100 Subject: NFC: llcp: Service Name Lookup netlink interface This adds a netlink interface for service name lookup support. Multiple URIs can be passed nested into the NFC_ATTR_LLC_SDP attribute using the NFC_CMD_LLC_SDREQ netlink command. When the SNL reply is received, a NFC_EVENT_LLC_SDRES event is sent to the user space. URI and SAP tuples are passed back, nested into NFC_ATTR_LLC_SDP attribute. Signed-off-by: Thierry Escande Signed-off-by: Samuel Ortiz --- include/uapi/linux/nfc.h | 12 ++++ net/nfc/llcp/commands.c | 78 ++++++++++++++++++++++ net/nfc/llcp/llcp.c | 32 +++++++++ net/nfc/llcp/llcp.h | 9 +++ net/nfc/netlink.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++ net/nfc/nfc.h | 14 ++++ 6 files changed, 314 insertions(+) (limited to 'include') diff --git a/include/uapi/linux/nfc.h b/include/uapi/linux/nfc.h index 855630fe731d..7440bc81a04b 100644 --- a/include/uapi/linux/nfc.h +++ b/include/uapi/linux/nfc.h @@ -90,6 +90,8 @@ enum nfc_commands { NFC_CMD_LLC_SET_PARAMS, NFC_CMD_ENABLE_SE, NFC_CMD_DISABLE_SE, + NFC_CMD_LLC_SDREQ, + NFC_EVENT_LLC_SDRES, /* private: internal use only */ __NFC_CMD_AFTER_LAST }; @@ -140,11 +142,21 @@ enum nfc_attrs { NFC_ATTR_LLC_PARAM_RW, NFC_ATTR_LLC_PARAM_MIUX, NFC_ATTR_SE, + NFC_ATTR_LLC_SDP, /* private: internal use only */ __NFC_ATTR_AFTER_LAST }; #define NFC_ATTR_MAX (__NFC_ATTR_AFTER_LAST - 1) +enum nfc_sdp_attr { + NFC_SDP_ATTR_UNSPEC, + NFC_SDP_ATTR_URI, + NFC_SDP_ATTR_SAP, +/* private: internal use only */ + __NFC_SDP_ATTR_AFTER_LAST +}; +#define NFC_SDP_ATTR_MAX (__NFC_SDP_ATTR_AFTER_LAST - 1) + #define NFC_DEVICE_NAME_MAXSIZE 8 #define NFC_NFCID1_MAXSIZE 10 #define NFC_SENSB_RES_MAXSIZE 12 diff --git a/net/nfc/llcp/commands.c b/net/nfc/llcp/commands.c index 59f7ffca783e..c943edb07b72 100644 --- a/net/nfc/llcp/commands.c +++ b/net/nfc/llcp/commands.c @@ -144,12 +144,59 @@ struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdres_tlv(u8 tid, u8 sap) return sdres; } +struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdreq_tlv(u8 tid, char *uri, + size_t uri_len) +{ + struct nfc_llcp_sdp_tlv *sdreq; + + pr_debug("uri: %s, len: %zu\n", uri, uri_len); + + sdreq = kzalloc(sizeof(struct nfc_llcp_sdp_tlv), GFP_KERNEL); + if (sdreq == NULL) + return NULL; + + sdreq->tlv_len = uri_len + 3; + + if (uri[uri_len - 1] == 0) + sdreq->tlv_len--; + + sdreq->tlv = kzalloc(sdreq->tlv_len + 1, GFP_KERNEL); + if (sdreq->tlv == NULL) { + kfree(sdreq); + return NULL; + } + + sdreq->tlv[0] = LLCP_TLV_SDREQ; + sdreq->tlv[1] = sdreq->tlv_len - 2; + sdreq->tlv[2] = tid; + + sdreq->tid = tid; + sdreq->uri = sdreq->tlv + 3; + memcpy(sdreq->uri, uri, uri_len); + + INIT_HLIST_NODE(&sdreq->node); + + return sdreq; +} + void nfc_llcp_free_sdp_tlv(struct nfc_llcp_sdp_tlv *sdp) { kfree(sdp->tlv); kfree(sdp); } +void nfc_llcp_free_sdp_tlv_list(struct hlist_head *head) +{ + struct nfc_llcp_sdp_tlv *sdp; + struct hlist_node *n; + + hlist_for_each_entry_safe(sdp, n, head, node) { + hlist_del(&sdp->node); + + nfc_llcp_free_sdp_tlv(sdp); + } +} + int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local, u8 *tlv_array, u16 tlv_array_len) { @@ -511,6 +558,37 @@ int nfc_llcp_send_snl_sdres(struct nfc_llcp_local *local, return 0; } +int nfc_llcp_send_snl_sdreq(struct nfc_llcp_local *local, + struct hlist_head *tlv_list, size_t tlvs_len) +{ + struct nfc_llcp_sdp_tlv *sdreq; + struct hlist_node *n; + struct sk_buff *skb; + + skb = nfc_llcp_allocate_snl(local, tlvs_len); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + mutex_lock(&local->sdreq_lock); + + hlist_for_each_entry_safe(sdreq, n, tlv_list, node) { + pr_debug("tid %d for %s\n", sdreq->tid, sdreq->uri); + + memcpy(skb_put(skb, sdreq->tlv_len), sdreq->tlv, + sdreq->tlv_len); + + hlist_del(&sdreq->node); + + hlist_add_head(&sdreq->node, &local->pending_sdreqs); + } + + mutex_unlock(&local->sdreq_lock); + + skb_queue_tail(&local->tx_queue, skb); + + return 0; +} + int nfc_llcp_send_dm(struct nfc_llcp_local *local, u8 ssap, u8 dsap, u8 reason) { struct sk_buff *skb; diff --git a/net/nfc/llcp/llcp.c b/net/nfc/llcp/llcp.c index 30b61c1aa984..99e911060422 100644 --- a/net/nfc/llcp/llcp.c +++ b/net/nfc/llcp/llcp.c @@ -156,6 +156,7 @@ static void local_release(struct kref *ref) cancel_work_sync(&local->rx_work); cancel_work_sync(&local->timeout_work); kfree_skb(local->rx_pending); + nfc_llcp_free_sdp_tlv_list(&local->pending_sdreqs); kfree(local); } @@ -1147,6 +1148,7 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local, struct nfc_llcp_sdp_tlv *sdp; HLIST_HEAD(llc_sdres_list); size_t sdres_tlvs_len; + HLIST_HEAD(nl_sdres_list); dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); @@ -1229,6 +1231,30 @@ add_snl: hlist_add_head(&sdp->node, &llc_sdres_list); break; + case LLCP_TLV_SDRES: + mutex_lock(&local->sdreq_lock); + + pr_debug("LLCP_TLV_SDRES: searching tid %d\n", tlv[2]); + + hlist_for_each_entry(sdp, &local->pending_sdreqs, node) { + if (sdp->tid != tlv[2]) + continue; + + sdp->sap = tlv[3]; + + pr_debug("Found: uri=%s, sap=%d\n", + sdp->uri, sdp->sap); + + hlist_del(&sdp->node); + + hlist_add_head(&sdp->node, &nl_sdres_list); + + break; + } + + mutex_unlock(&local->sdreq_lock); + break; + default: pr_err("Invalid SNL tlv value 0x%x\n", type); break; @@ -1239,6 +1265,9 @@ add_snl: } exit: + if (!hlist_empty(&nl_sdres_list)) + nfc_genl_llc_send_sdres(local->dev, &nl_sdres_list); + if (!hlist_empty(&llc_sdres_list)) nfc_llcp_send_snl_sdres(local, &llc_sdres_list, sdres_tlvs_len); } @@ -1426,6 +1455,9 @@ int nfc_llcp_register_device(struct nfc_dev *ndev) local->remote_miu = LLCP_DEFAULT_MIU; local->remote_lto = LLCP_DEFAULT_LTO; + mutex_init(&local->sdreq_lock); + INIT_HLIST_HEAD(&local->pending_sdreqs); + list_add(&local->list, &llcp_devices); return 0; diff --git a/net/nfc/llcp/llcp.h b/net/nfc/llcp/llcp.h index 465f5953e2d1..ca8c6d94ab85 100644 --- a/net/nfc/llcp/llcp.h +++ b/net/nfc/llcp/llcp.h @@ -97,6 +97,10 @@ struct nfc_llcp_local { u8 remote_opt; u16 remote_wks; + struct mutex sdreq_lock; + struct hlist_head pending_sdreqs; + u8 sdreq_next_tid; + /* sockets array */ struct llcp_sock_list sockets; struct llcp_sock_list connecting_sockets; @@ -230,7 +234,10 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock, void nfc_llcp_recv(void *data, struct sk_buff *skb, int err); u8 *nfc_llcp_build_tlv(u8 type, u8 *value, u8 value_length, u8 *tlv_length); struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdres_tlv(u8 tid, u8 sap); +struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdreq_tlv(u8 tid, char *uri, + size_t uri_len); void nfc_llcp_free_sdp_tlv(struct nfc_llcp_sdp_tlv *sdp); +void nfc_llcp_free_sdp_tlv_list(struct hlist_head *sdp_head); void nfc_llcp_recv(void *data, struct sk_buff *skb, int err); int nfc_llcp_disconnect(struct nfc_llcp_sock *sock); int nfc_llcp_send_symm(struct nfc_dev *dev); @@ -238,6 +245,8 @@ int nfc_llcp_send_connect(struct nfc_llcp_sock *sock); int nfc_llcp_send_cc(struct nfc_llcp_sock *sock); int nfc_llcp_send_snl_sdres(struct nfc_llcp_local *local, struct hlist_head *tlv_list, size_t tlvs_len); +int nfc_llcp_send_snl_sdreq(struct nfc_llcp_local *local, + struct hlist_head *tlv_list, size_t tlvs_len); int nfc_llcp_send_dm(struct nfc_llcp_local *local, u8 ssap, u8 dsap, u8 reason); int nfc_llcp_send_disconnect(struct nfc_llcp_sock *sock); int nfc_llcp_send_i_frame(struct nfc_llcp_sock *sock, diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index 63975c039b85..73fd51098f4d 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -56,6 +56,12 @@ static const struct nla_policy nfc_genl_policy[NFC_ATTR_MAX + 1] = { [NFC_ATTR_LLC_PARAM_LTO] = { .type = NLA_U8 }, [NFC_ATTR_LLC_PARAM_RW] = { .type = NLA_U8 }, [NFC_ATTR_LLC_PARAM_MIUX] = { .type = NLA_U16 }, + [NFC_ATTR_LLC_SDP] = { .type = NLA_NESTED }, +}; + +static const struct nla_policy nfc_sdp_genl_policy[NFC_SDP_ATTR_MAX + 1] = { + [NFC_SDP_ATTR_URI] = { .type = NLA_STRING }, + [NFC_SDP_ATTR_SAP] = { .type = NLA_U8 }, }; static int nfc_genl_send_target(struct sk_buff *msg, struct nfc_target *target, @@ -351,6 +357,74 @@ free_msg: return -EMSGSIZE; } +int nfc_genl_llc_send_sdres(struct nfc_dev *dev, struct hlist_head *sdres_list) +{ + struct sk_buff *msg; + struct nlattr *sdp_attr, *uri_attr; + struct nfc_llcp_sdp_tlv *sdres; + struct hlist_node *n; + void *hdr; + int rc = -EMSGSIZE; + int i; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &nfc_genl_family, 0, + NFC_EVENT_LLC_SDRES); + if (!hdr) + goto free_msg; + + if (nla_put_u32(msg, NFC_ATTR_DEVICE_INDEX, dev->idx)) + goto nla_put_failure; + + sdp_attr = nla_nest_start(msg, NFC_ATTR_LLC_SDP); + if (sdp_attr == NULL) { + rc = -ENOMEM; + goto nla_put_failure; + } + + i = 1; + hlist_for_each_entry_safe(sdres, n, sdres_list, node) { + pr_debug("uri: %s, sap: %d\n", sdres->uri, sdres->sap); + + uri_attr = nla_nest_start(msg, i++); + if (uri_attr == NULL) { + rc = -ENOMEM; + goto nla_put_failure; + } + + if (nla_put_u8(msg, NFC_SDP_ATTR_SAP, sdres->sap)) + goto nla_put_failure; + + if (nla_put_string(msg, NFC_SDP_ATTR_URI, sdres->uri)) + goto nla_put_failure; + + nla_nest_end(msg, uri_attr); + + hlist_del(&sdres->node); + + nfc_llcp_free_sdp_tlv(sdres); + } + + nla_nest_end(msg, sdp_attr); + + genlmsg_end(msg, hdr); + + return genlmsg_multicast(msg, 0, nfc_genl_event_mcgrp.id, GFP_ATOMIC); + +nla_put_failure: + genlmsg_cancel(msg, hdr); + +free_msg: + nlmsg_free(msg); + + nfc_llcp_free_sdp_tlv_list(sdres_list); + + return rc; +} + static int nfc_genl_send_device(struct sk_buff *msg, struct nfc_dev *dev, u32 portid, u32 seq, struct netlink_callback *cb, @@ -862,6 +936,96 @@ exit: return rc; } +static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) +{ + struct nfc_dev *dev; + struct nfc_llcp_local *local; + struct nlattr *attr, *sdp_attrs[NFC_SDP_ATTR_MAX+1]; + u32 idx; + u8 tid; + char *uri; + int rc = 0, rem; + size_t uri_len, tlvs_len; + struct hlist_head sdreq_list; + struct nfc_llcp_sdp_tlv *sdreq; + + if (!info->attrs[NFC_ATTR_DEVICE_INDEX] || + !info->attrs[NFC_ATTR_LLC_SDP]) + return -EINVAL; + + idx = nla_get_u32(info->attrs[NFC_ATTR_DEVICE_INDEX]); + + dev = nfc_get_device(idx); + if (!dev) { + rc = -ENODEV; + goto exit; + } + + device_lock(&dev->dev); + + if (dev->dep_link_up == false) { + rc = -ENOLINK; + goto exit; + } + + local = nfc_llcp_find_local(dev); + if (!local) { + nfc_put_device(dev); + rc = -ENODEV; + goto exit; + } + + INIT_HLIST_HEAD(&sdreq_list); + + tlvs_len = 0; + + nla_for_each_nested(attr, info->attrs[NFC_ATTR_LLC_SDP], rem) { + rc = nla_parse_nested(sdp_attrs, NFC_SDP_ATTR_MAX, attr, + nfc_sdp_genl_policy); + + if (rc != 0) { + rc = -EINVAL; + goto exit; + } + + if (!sdp_attrs[NFC_SDP_ATTR_URI]) + continue; + + uri_len = nla_len(sdp_attrs[NFC_SDP_ATTR_URI]); + if (uri_len == 0) + continue; + + uri = nla_data(sdp_attrs[NFC_SDP_ATTR_URI]); + if (uri == NULL || *uri == 0) + continue; + + tid = local->sdreq_next_tid++; + + sdreq = nfc_llcp_build_sdreq_tlv(tid, uri, uri_len); + if (sdreq == NULL) { + rc = -ENOMEM; + goto exit; + } + + tlvs_len += sdreq->tlv_len; + + hlist_add_head(&sdreq->node, &sdreq_list); + } + + if (hlist_empty(&sdreq_list)) { + rc = -EINVAL; + goto exit; + } + + rc = nfc_llcp_send_snl_sdreq(local, &sdreq_list, tlvs_len); +exit: + device_unlock(&dev->dev); + + nfc_put_device(dev); + + return rc; +} + static struct genl_ops nfc_genl_ops[] = { { .cmd = NFC_CMD_GET_DEVICE, @@ -916,6 +1080,11 @@ static struct genl_ops nfc_genl_ops[] = { .doit = nfc_genl_llc_set_params, .policy = nfc_genl_policy, }, + { + .cmd = NFC_CMD_LLC_SDREQ, + .doit = nfc_genl_llc_sdreq, + .policy = nfc_genl_policy, + }, }; diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h index 87d914d2876a..94bfe19ba678 100644 --- a/net/nfc/nfc.h +++ b/net/nfc/nfc.h @@ -46,6 +46,8 @@ struct nfc_rawsock { #define to_rawsock_sk(_tx_work) \ ((struct sock *) container_of(_tx_work, struct nfc_rawsock, tx_work)) +struct nfc_llcp_sdp_tlv; + #ifdef CONFIG_NFC_LLCP void nfc_llcp_mac_is_down(struct nfc_dev *dev); @@ -59,6 +61,8 @@ int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb); struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev); int __init nfc_llcp_init(void); void nfc_llcp_exit(void); +void nfc_llcp_free_sdp_tlv(struct nfc_llcp_sdp_tlv *sdp); +void nfc_llcp_free_sdp_tlv_list(struct hlist_head *head); #else @@ -112,6 +116,14 @@ static inline void nfc_llcp_exit(void) { } +static inline void nfc_llcp_free_sdp_tlv(struct nfc_llcp_sdp_tlv *sdp) +{ +} + +static inline void nfc_llcp_free_sdp_tlv_list(struct hlist_head *sdp_head) +{ +} + #endif int __init rawsock_init(void); @@ -144,6 +156,8 @@ int nfc_genl_dep_link_down_event(struct nfc_dev *dev); int nfc_genl_tm_activated(struct nfc_dev *dev, u32 protocol); int nfc_genl_tm_deactivated(struct nfc_dev *dev); +int nfc_genl_llc_send_sdres(struct nfc_dev *dev, struct hlist_head *sdres_list); + struct nfc_dev *nfc_get_device(unsigned int idx); static inline void nfc_put_device(struct nfc_dev *dev) -- cgit v1.2.3 From 2908fe31cf6b8d3a975efb567347f85e724f4e81 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 15 Mar 2013 17:06:56 -0500 Subject: Bluetooth: Remove useless HCI_PENDING_CLASS flag Now that class related operations are tracked through asynchronous HCI requests this flag is no longer needed. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci.h | 1 - net/bluetooth/hci_event.c | 3 +-- net/bluetooth/mgmt.c | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 7f12c25f1fca..1e723c76a8f6 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -119,7 +119,6 @@ enum { HCI_CONNECTABLE, HCI_DISCOVERABLE, HCI_LINK_SECURITY, - HCI_PENDING_CLASS, HCI_PERIODIC_INQ, }; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index d11b87bc1d1a..5f2d008f3352 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -194,8 +194,7 @@ static void hci_cc_reset(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_RESET, &hdev->flags); /* Reset all non-persistent flags */ - hdev->dev_flags &= ~(BIT(HCI_LE_SCAN) | BIT(HCI_PENDING_CLASS) | - BIT(HCI_PERIODIC_INQ)); + hdev->dev_flags &= ~(BIT(HCI_LE_SCAN) | BIT(HCI_PERIODIC_INQ)); hdev->discovery.state = DISCOVERY_STOPPED; hdev->inq_tx_power = HCI_TX_POWER_INVALID; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 8a0bbb914bed..3e3cb0102b13 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -652,8 +652,6 @@ static void update_class(struct hci_request *req) return; hci_req_add(req, HCI_OP_WRITE_CLASS_OF_DEV, sizeof(cod), cod); - - set_bit(HCI_PENDING_CLASS, &hdev->dev_flags); } static void service_cache_off(struct work_struct *work) @@ -3762,8 +3760,6 @@ int mgmt_set_class_of_dev_complete(struct hci_dev *hdev, u8 *dev_class, struct cmd_lookup match = { NULL, hdev, mgmt_status(status) }; int err = 0; - clear_bit(HCI_PENDING_CLASS, &hdev->dev_flags); - mgmt_pending_foreach(MGMT_OP_SET_DEV_CLASS, hdev, sk_lookup, &match); mgmt_pending_foreach(MGMT_OP_ADD_UUID, hdev, sk_lookup, &match); mgmt_pending_foreach(MGMT_OP_REMOVE_UUID, hdev, sk_lookup, &match); -- cgit v1.2.3 From 2cc6fb0049bc02ca7a020ba7b4f88b4c35976058 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 15 Mar 2013 17:06:57 -0500 Subject: Bluetooth: Add a define for the HCI persistent flags mask We'll need to use this mask also when powering off the HCI device so it's better to have this in a single and visible place. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci.h | 5 +++++ net/bluetooth/hci_event.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 1e723c76a8f6..1e40222e9dd0 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -122,6 +122,11 @@ enum { HCI_PERIODIC_INQ, }; +/* A mask for the flags that are supposed to remain when a reset happens + * or the HCI device is closed. + */ +#define HCI_PERSISTENT_MASK (BIT(HCI_LE_SCAN) | BIT(HCI_PERIODIC_INQ)) + /* HCI ioctl defines */ #define HCIDEVUP _IOW('H', 201, int) #define HCIDEVDOWN _IOW('H', 202, int) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 5f2d008f3352..ed4ecd930a71 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -194,7 +194,7 @@ static void hci_cc_reset(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_RESET, &hdev->flags); /* Reset all non-persistent flags */ - hdev->dev_flags &= ~(BIT(HCI_LE_SCAN) | BIT(HCI_PERIODIC_INQ)); + hdev->dev_flags &= ~HCI_PERSISTENT_MASK; hdev->discovery.state = DISCOVERY_STOPPED; hdev->inq_tx_power = HCI_TX_POWER_INVALID; -- cgit v1.2.3 From 04b4edcbc9049e100681c0149b572de439be42ab Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 15 Mar 2013 17:07:01 -0500 Subject: Bluetooth: Handle AD updating through an async request For proper control of the AD update and the related HCI commands it's best to run the AD update through an async request instead of a standalone HCI command. This patch changes the hci_update_ad() function to take a request pointer and updates its users appropriately. E.g. the function is no longer called after the init sequence but during stage 3 of the init sequence. The TX power is read during the init sequence, so we don't need an explicit update whenever it is read and the AD update based on the local name should be done through the local name mgmt handler. The only other user is the update based on enabling advertising. This part is still kept as there is no mgmt API to enable it. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci_core.h | 4 ++-- net/bluetooth/hci_core.c | 29 ++++++++++------------------- net/bluetooth/hci_event.c | 19 +++++++++---------- 3 files changed, 21 insertions(+), 31 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index d6c32561fc02..cb99193c7493 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -740,8 +740,6 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *hash, u8 *randomizer); int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr); -int hci_update_ad(struct hci_dev *hdev); - void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb); int hci_recv_frame(struct sk_buff *skb); @@ -1167,6 +1165,8 @@ struct hci_sec_filter { #define hci_req_lock(d) mutex_lock(&d->req_lock) #define hci_req_unlock(d) mutex_unlock(&d->req_lock) +void hci_update_ad(struct hci_request *req); + void hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, u16 to_multiplier); void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __u8 rand[8], diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 9e87a91562a6..0ffd35871172 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -492,8 +492,10 @@ static void hci_init3_req(struct hci_request *req, unsigned long opt) if (hdev->commands[5] & 0x10) hci_setup_link_policy(req); - if (lmp_le_capable(hdev)) + if (lmp_le_capable(hdev)) { hci_set_le_support(req); + hci_update_ad(req); + } } static int __hci_init(struct hci_dev *hdev) @@ -936,39 +938,29 @@ static u8 create_ad(struct hci_dev *hdev, u8 *ptr) return ad_len; } -int hci_update_ad(struct hci_dev *hdev) +void hci_update_ad(struct hci_request *req) { + struct hci_dev *hdev = req->hdev; struct hci_cp_le_set_adv_data cp; u8 len; - int err; - - hci_dev_lock(hdev); - if (!lmp_le_capable(hdev)) { - err = -EINVAL; - goto unlock; - } + if (!lmp_le_capable(hdev)) + return; memset(&cp, 0, sizeof(cp)); len = create_ad(hdev, cp.data); if (hdev->adv_data_len == len && - memcmp(cp.data, hdev->adv_data, len) == 0) { - err = 0; - goto unlock; - } + memcmp(cp.data, hdev->adv_data, len) == 0) + return; memcpy(hdev->adv_data, cp.data, sizeof(cp.data)); hdev->adv_data_len = len; cp.length = len; - err = hci_send_cmd(hdev, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp); -unlock: - hci_dev_unlock(hdev); - - return err; + hci_req_add(req, HCI_OP_LE_SET_ADV_DATA, sizeof(cp), &cp); } /* ---- HCI ioctl helpers ---- */ @@ -1025,7 +1017,6 @@ int hci_dev_open(__u16 dev) hci_dev_hold(hdev); set_bit(HCI_UP, &hdev->flags); hci_notify(hdev, HCI_DEV_UP); - hci_update_ad(hdev); if (!test_bit(HCI_SETUP, &hdev->dev_flags) && mgmt_valid_hdev(hdev)) { hci_dev_lock(hdev); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index ed4ecd930a71..84edacbc14a1 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -223,9 +223,6 @@ static void hci_cc_write_local_name(struct hci_dev *hdev, struct sk_buff *skb) memcpy(hdev->dev_name, sent, HCI_MAX_NAME_LENGTH); hci_dev_unlock(hdev); - - if (!status && !test_bit(HCI_INIT, &hdev->flags)) - hci_update_ad(hdev); } static void hci_cc_read_local_name(struct hci_dev *hdev, struct sk_buff *skb) @@ -776,11 +773,8 @@ static void hci_cc_le_read_adv_tx_power(struct hci_dev *hdev, BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); - if (!rp->status) { + if (!rp->status) hdev->adv_tx_power = rp->tx_power; - if (!test_bit(HCI_INIT, &hdev->flags)) - hci_update_ad(hdev); - } } static void hci_cc_user_confirm_reply(struct hci_dev *hdev, struct sk_buff *skb) @@ -877,10 +871,15 @@ static void hci_cc_le_set_adv_enable(struct hci_dev *hdev, struct sk_buff *skb) clear_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags); } - hci_dev_unlock(hdev); + if (!test_bit(HCI_INIT, &hdev->flags)) { + struct hci_request req; - if (!test_bit(HCI_INIT, &hdev->flags)) - hci_update_ad(hdev); + hci_req_init(&req, hdev); + hci_update_ad(&req); + hci_req_run(&req, NULL); + } + + hci_dev_unlock(hdev); } static void hci_cc_le_set_scan_param(struct hci_dev *hdev, struct sk_buff *skb) -- cgit v1.2.3 From 1a4d3c4b3750885733641216756de4e4d9b2443a Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 15 Mar 2013 17:07:08 -0500 Subject: Bluetooth: Add proper flag for fast connectable mode In order to be able to represent fast connectable mode in the mgmt settings we need to have a HCI dev flag for it. This patch adds the flag and makes sure its value is changed whenever a mgmt_set_fast_connectable command completes. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci.h | 4 +++- net/bluetooth/mgmt.c | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 1e40222e9dd0..b854506c859c 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -120,12 +120,14 @@ enum { HCI_DISCOVERABLE, HCI_LINK_SECURITY, HCI_PERIODIC_INQ, + HCI_FAST_CONNECTABLE, }; /* A mask for the flags that are supposed to remain when a reset happens * or the HCI device is closed. */ -#define HCI_PERSISTENT_MASK (BIT(HCI_LE_SCAN) | BIT(HCI_PERIODIC_INQ)) +#define HCI_PERSISTENT_MASK (BIT(HCI_LE_SCAN) | BIT(HCI_PERIODIC_INQ) | \ + BIT(HCI_FAST_CONNECTABLE)) /* HCI ioctl defines */ #define HCIDEVUP _IOW('H', 201, int) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index e89938e0233c..b6a33c5e7685 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -410,6 +410,9 @@ static u32 get_current_settings(struct hci_dev *hdev) if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags)) settings |= MGMT_SETTING_CONNECTABLE; + if (test_bit(HCI_FAST_CONNECTABLE, &hdev->dev_flags)) + settings |= MGMT_SETTING_FAST_CONNECTABLE; + if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) settings |= MGMT_SETTING_DISCOVERABLE; @@ -2913,6 +2916,13 @@ static void fast_connectable_complete(struct hci_dev *hdev, u8 status) cmd_status(cmd->sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE, mgmt_status(status)); } else { + struct mgmt_mode *cp = cmd->param; + + if (cp->val) + set_bit(HCI_FAST_CONNECTABLE, &hdev->dev_flags); + else + clear_bit(HCI_FAST_CONNECTABLE, &hdev->dev_flags); + send_settings_rsp(cmd->sk, MGMT_OP_SET_FAST_CONNECTABLE, hdev); new_settings(hdev, cmd->sk); } @@ -2959,6 +2969,12 @@ static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev, goto unlock; } + if (!!cp->val == test_bit(HCI_FAST_CONNECTABLE, &hdev->dev_flags)) { + err = send_settings_rsp(sk, MGMT_OP_SET_FAST_CONNECTABLE, + hdev); + goto unlock; + } + if (cp->val) { type = PAGE_SCAN_TYPE_INTERLACED; -- cgit v1.2.3 From f332ec6699980e0563408c7bcf1a8a31b825fee1 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 15 Mar 2013 17:07:11 -0500 Subject: Bluetooth: Add reading of page scan parameters These parameters are related to the "fast connectable" mode that can be changed through the mgmt interface. Not all controllers properly reset these values with HCI_Reset so they need to be read in order to be able to verify whether the values are correct or not before enabling page scan. Signed-off-by: Johan Hedberg Acked-by: Marcel Holtmann Signed-off-by: Gustavo Padovan --- include/net/bluetooth/hci.h | 13 +++++++++++++ include/net/bluetooth/hci_core.h | 4 ++++ net/bluetooth/hci_core.c | 6 ++++++ net/bluetooth/hci_event.c | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+) (limited to 'include') diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index b854506c859c..b3308927a0a1 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -887,12 +887,25 @@ struct hci_rp_read_data_block_size { __le16 num_blocks; } __packed; +#define HCI_OP_READ_PAGE_SCAN_ACTIVITY 0x0c1b +struct hci_rp_read_page_scan_activity { + __u8 status; + __le16 interval; + __le16 window; +} __packed; + #define HCI_OP_WRITE_PAGE_SCAN_ACTIVITY 0x0c1c struct hci_cp_write_page_scan_activity { __le16 interval; __le16 window; } __packed; +#define HCI_OP_READ_PAGE_SCAN_TYPE 0x0c46 +struct hci_rp_read_page_scan_type { + __u8 status; + __u8 type; +} __packed; + #define HCI_OP_WRITE_PAGE_SCAN_TYPE 0x0c47 #define PAGE_SCAN_TYPE_STANDARD 0x00 #define PAGE_SCAN_TYPE_INTERLACED 0x01 diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index cb99193c7493..358a6983d3bb 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -165,6 +165,10 @@ struct hci_dev { __u16 voice_setting; __u8 io_capability; __s8 inq_tx_power; + __u16 page_scan_interval; + __u16 page_scan_window; + __u8 page_scan_type; + __u16 devid_source; __u16 devid_vendor; __u16 devid_product; diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 0ffd35871172..cfcad5423f1c 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -272,6 +272,12 @@ static void bredr_setup(struct hci_request *req) bacpy(&cp.bdaddr, BDADDR_ANY); cp.delete_all = 0x01; hci_req_add(req, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp); + + /* Read page scan parameters */ + if (req->hdev->hci_ver > BLUETOOTH_VER_1_1) { + hci_req_add(req, HCI_OP_READ_PAGE_SCAN_ACTIVITY, 0, NULL); + hci_req_add(req, HCI_OP_READ_PAGE_SCAN_TYPE, 0, NULL); + } } static void le_setup(struct hci_request *req) diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 84edacbc14a1..3c6d0a4f78dc 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -601,6 +601,30 @@ static void hci_cc_read_bd_addr(struct hci_dev *hdev, struct sk_buff *skb) bacpy(&hdev->bdaddr, &rp->bdaddr); } +static void hci_cc_read_page_scan_activity(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_rp_read_page_scan_activity *rp = (void *) skb->data; + + BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); + + if (test_bit(HCI_INIT, &hdev->flags) && !rp->status) { + hdev->page_scan_interval = __le16_to_cpu(rp->interval); + hdev->page_scan_window = __le16_to_cpu(rp->window); + } +} + +static void hci_cc_read_page_scan_type(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_rp_read_page_scan_type *rp = (void *) skb->data; + + BT_DBG("%s status 0x%2.2x", hdev->name, rp->status); + + if (test_bit(HCI_INIT, &hdev->flags) && !rp->status) + hdev->page_scan_type = rp->type; +} + static void hci_cc_read_data_block_size(struct hci_dev *hdev, struct sk_buff *skb) { @@ -2204,6 +2228,14 @@ static void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_cc_read_bd_addr(hdev, skb); break; + case HCI_OP_READ_PAGE_SCAN_ACTIVITY: + hci_cc_read_page_scan_activity(hdev, skb); + break; + + case HCI_OP_READ_PAGE_SCAN_TYPE: + hci_cc_read_page_scan_type(hdev, skb); + break; + case HCI_OP_READ_DATA_BLOCK_SIZE: hci_cc_read_data_block_size(hdev, skb); break; -- cgit v1.2.3 From c5116e9d8d2de324f13a91fe5afc308cd6b0ca93 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Tue, 19 Mar 2013 16:58:58 +0100 Subject: ssb: define more board types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rafał Miłecki Signed-off-by: John W. Linville --- include/linux/ssb/ssb.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h index 8b1322296fed..c64999fd1660 100644 --- a/include/linux/ssb/ssb.h +++ b/include/linux/ssb/ssb.h @@ -340,13 +340,61 @@ enum ssb_bustype { #define SSB_BOARDVENDOR_DELL 0x1028 /* Dell */ #define SSB_BOARDVENDOR_HP 0x0E11 /* HP */ /* board_type */ +#define SSB_BOARD_BCM94301CB 0x0406 +#define SSB_BOARD_BCM94301MP 0x0407 +#define SSB_BOARD_BU4309 0x040A +#define SSB_BOARD_BCM94309CB 0x040B +#define SSB_BOARD_BCM4309MP 0x040C +#define SSB_BOARD_BU4306 0x0416 #define SSB_BOARD_BCM94306MP 0x0418 #define SSB_BOARD_BCM4309G 0x0421 #define SSB_BOARD_BCM4306CB 0x0417 -#define SSB_BOARD_BCM4309MP 0x040C +#define SSB_BOARD_BCM94306PC 0x0425 /* pcmcia 3.3v 4306 card */ +#define SSB_BOARD_BCM94306CBSG 0x042B /* with SiGe PA */ +#define SSB_BOARD_PCSG94306 0x042D /* with SiGe PA */ +#define SSB_BOARD_BU4704SD 0x042E /* with sdram */ +#define SSB_BOARD_BCM94704AGR 0x042F /* dual 11a/11g Router */ +#define SSB_BOARD_BCM94308MP 0x0430 /* 11a-only minipci */ +#define SSB_BOARD_BU4318 0x0447 +#define SSB_BOARD_CB4318 0x0448 +#define SSB_BOARD_MPG4318 0x0449 #define SSB_BOARD_MP4318 0x044A -#define SSB_BOARD_BU4306 0x0416 -#define SSB_BOARD_BU4309 0x040A +#define SSB_BOARD_SD4318 0x044B +#define SSB_BOARD_BCM94306P 0x044C /* with SiGe */ +#define SSB_BOARD_BCM94303MP 0x044E +#define SSB_BOARD_BCM94306MPM 0x0450 +#define SSB_BOARD_BCM94306MPL 0x0453 +#define SSB_BOARD_PC4303 0x0454 /* pcmcia */ +#define SSB_BOARD_BCM94306MPLNA 0x0457 +#define SSB_BOARD_BCM94306MPH 0x045B +#define SSB_BOARD_BCM94306PCIV 0x045C +#define SSB_BOARD_BCM94318MPGH 0x0463 +#define SSB_BOARD_BU4311 0x0464 +#define SSB_BOARD_BCM94311MC 0x0465 +#define SSB_BOARD_BCM94311MCAG 0x0466 +/* 4321 boards */ +#define SSB_BOARD_BU4321 0x046B +#define SSB_BOARD_BU4321E 0x047C +#define SSB_BOARD_MP4321 0x046C +#define SSB_BOARD_CB2_4321 0x046D +#define SSB_BOARD_CB2_4321_AG 0x0066 +#define SSB_BOARD_MC4321 0x046E +/* 4325 boards */ +#define SSB_BOARD_BCM94325DEVBU 0x0490 +#define SSB_BOARD_BCM94325BGABU 0x0491 +#define SSB_BOARD_BCM94325SDGWB 0x0492 +#define SSB_BOARD_BCM94325SDGMDL 0x04AA +#define SSB_BOARD_BCM94325SDGMDL2 0x04C6 +#define SSB_BOARD_BCM94325SDGMDL3 0x04C9 +#define SSB_BOARD_BCM94325SDABGWBA 0x04E1 +/* 4322 boards */ +#define SSB_BOARD_BCM94322MC 0x04A4 +#define SSB_BOARD_BCM94322USB 0x04A8 /* dualband */ +#define SSB_BOARD_BCM94322HM 0x04B0 +#define SSB_BOARD_BCM94322USB2D 0x04Bf /* single band discrete front end */ +/* 4312 boards */ +#define SSB_BOARD_BU4312 0x048A +#define SSB_BOARD_BCM4312MCGSG 0x04B5 /* chip_package */ #define SSB_CHIPPACK_BCM4712S 1 /* Small 200pin 4712 */ #define SSB_CHIPPACK_BCM4712M 2 /* Medium 225pin 4712 */ -- cgit v1.2.3 From 3e6998574fde0ab7a3329c9229394dd80462ead2 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Tue, 19 Mar 2013 16:58:59 +0100 Subject: bcma: define board types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using that IDs we can write workarounds for various cards Signed-off-by: Rafał Miłecki Signed-off-by: John W. Linville --- include/linux/bcma/bcma.h | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'include') diff --git a/include/linux/bcma/bcma.h b/include/linux/bcma/bcma.h index e0ce311011c0..0ab6712fd76b 100644 --- a/include/linux/bcma/bcma.h +++ b/include/linux/bcma/bcma.h @@ -173,6 +173,60 @@ struct bcma_host_ops { #define BCMA_CHIP_ID_BCM53572 53572 #define BCMA_PKG_ID_BCM47188 9 +/* Board types (on PCI usually equals to the subsystem dev id) */ +/* BCM4313 */ +#define BCMA_BOARD_TYPE_BCM94313BU 0X050F +#define BCMA_BOARD_TYPE_BCM94313HM 0X0510 +#define BCMA_BOARD_TYPE_BCM94313EPA 0X0511 +#define BCMA_BOARD_TYPE_BCM94313HMG 0X051C +/* BCM4716 */ +#define BCMA_BOARD_TYPE_BCM94716NR2 0X04CD +/* BCM43224 */ +#define BCMA_BOARD_TYPE_BCM943224X21 0X056E +#define BCMA_BOARD_TYPE_BCM943224X21_FCC 0X00D1 +#define BCMA_BOARD_TYPE_BCM943224X21B 0X00E9 +#define BCMA_BOARD_TYPE_BCM943224M93 0X008B +#define BCMA_BOARD_TYPE_BCM943224M93A 0X0090 +#define BCMA_BOARD_TYPE_BCM943224X16 0X0093 +#define BCMA_BOARD_TYPE_BCM94322X9 0X008D +#define BCMA_BOARD_TYPE_BCM94322M35E 0X008E +/* BCM43228 */ +#define BCMA_BOARD_TYPE_BCM943228BU8 0X0540 +#define BCMA_BOARD_TYPE_BCM943228BU9 0X0541 +#define BCMA_BOARD_TYPE_BCM943228BU 0X0542 +#define BCMA_BOARD_TYPE_BCM943227HM4L 0X0543 +#define BCMA_BOARD_TYPE_BCM943227HMB 0X0544 +#define BCMA_BOARD_TYPE_BCM943228HM4L 0X0545 +#define BCMA_BOARD_TYPE_BCM943228SD 0X0573 +/* BCM4331 */ +#define BCMA_BOARD_TYPE_BCM94331X19 0X00D6 +#define BCMA_BOARD_TYPE_BCM94331X28 0X00E4 +#define BCMA_BOARD_TYPE_BCM94331X28B 0X010E +#define BCMA_BOARD_TYPE_BCM94331PCIEBT3AX 0X00E4 +#define BCMA_BOARD_TYPE_BCM94331X12_2G 0X00EC +#define BCMA_BOARD_TYPE_BCM94331X12_5G 0X00ED +#define BCMA_BOARD_TYPE_BCM94331X29B 0X00EF +#define BCMA_BOARD_TYPE_BCM94331CSAX 0X00EF +#define BCMA_BOARD_TYPE_BCM94331X19C 0X00F5 +#define BCMA_BOARD_TYPE_BCM94331X33 0X00F4 +#define BCMA_BOARD_TYPE_BCM94331BU 0X0523 +#define BCMA_BOARD_TYPE_BCM94331S9BU 0X0524 +#define BCMA_BOARD_TYPE_BCM94331MC 0X0525 +#define BCMA_BOARD_TYPE_BCM94331MCI 0X0526 +#define BCMA_BOARD_TYPE_BCM94331PCIEBT4 0X0527 +#define BCMA_BOARD_TYPE_BCM94331HM 0X0574 +#define BCMA_BOARD_TYPE_BCM94331PCIEDUAL 0X059B +#define BCMA_BOARD_TYPE_BCM94331MCH5 0X05A9 +#define BCMA_BOARD_TYPE_BCM94331CS 0X05C6 +#define BCMA_BOARD_TYPE_BCM94331CD 0X05DA +/* BCM53572 */ +#define BCMA_BOARD_TYPE_BCM953572BU 0X058D +#define BCMA_BOARD_TYPE_BCM953572NR2 0X058E +#define BCMA_BOARD_TYPE_BCM947188NR2 0X058F +#define BCMA_BOARD_TYPE_BCM953572SDRNR2 0X0590 +/* BCM43142 */ +#define BCMA_BOARD_TYPE_BCM943142HM 0X05E0 + struct bcma_device { struct bcma_bus *bus; struct bcma_device_id id; -- cgit v1.2.3 From 12bef78f0a806639daef58b1770be6ea19b2e94d Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 21 Mar 2013 16:26:19 +0100 Subject: ssb: fix sprom constant for ant_available_{bg,a} This was done accordingly to new specs. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- include/linux/ssb/ssb_regs.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/ssb/ssb_regs.h b/include/linux/ssb/ssb_regs.h index 6ecfa02ddbac..3a7256955b10 100644 --- a/include/linux/ssb/ssb_regs.h +++ b/include/linux/ssb/ssb_regs.h @@ -289,11 +289,11 @@ #define SSB_SPROM4_ETHPHY_ET1A_SHIFT 5 #define SSB_SPROM4_ETHPHY_ET0M (1<<14) /* MDIO for enet0 */ #define SSB_SPROM4_ETHPHY_ET1M (1<<15) /* MDIO for enet1 */ -#define SSB_SPROM4_ANTAVAIL 0x005D /* Antenna available bitfields */ -#define SSB_SPROM4_ANTAVAIL_A 0x00FF /* A-PHY bitfield */ -#define SSB_SPROM4_ANTAVAIL_A_SHIFT 0 -#define SSB_SPROM4_ANTAVAIL_BG 0xFF00 /* B-PHY and G-PHY bitfield */ -#define SSB_SPROM4_ANTAVAIL_BG_SHIFT 8 +#define SSB_SPROM4_ANTAVAIL 0x005C /* Antenna available bitfields */ +#define SSB_SPROM4_ANTAVAIL_BG 0x00FF /* B-PHY and G-PHY bitfield */ +#define SSB_SPROM4_ANTAVAIL_BG_SHIFT 0 +#define SSB_SPROM4_ANTAVAIL_A 0xFF00 /* A-PHY bitfield */ +#define SSB_SPROM4_ANTAVAIL_A_SHIFT 8 #define SSB_SPROM4_AGAIN01 0x005E /* Antenna Gain (in dBm Q5.2) */ #define SSB_SPROM4_AGAIN0 0x00FF /* Antenna 0 */ #define SSB_SPROM4_AGAIN0_SHIFT 0 -- cgit v1.2.3 From d6b688cf2f7ca3e168acc73597f4d7102ae663fa Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 27 Mar 2013 17:23:10 +0100 Subject: bcma: handle more devices in bcma_pmu_get_alp_clock() Add some more chip IDs to bcma_pmu_get_alp_clock() Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/bcma/driver_chipcommon_pmu.c | 24 ++++++++++++++++++++---- include/linux/bcma/bcma_driver_chipcommon.h | 1 + 2 files changed, 21 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/bcma/driver_chipcommon_pmu.c b/drivers/bcma/driver_chipcommon_pmu.c index 7e88fffaf3f5..edca73af3cc0 100644 --- a/drivers/bcma/driver_chipcommon_pmu.c +++ b/drivers/bcma/driver_chipcommon_pmu.c @@ -174,19 +174,35 @@ u32 bcma_pmu_get_alp_clock(struct bcma_drv_cc *cc) struct bcma_bus *bus = cc->core->bus; switch (bus->chipinfo.id) { + case BCMA_CHIP_ID_BCM4313: + case BCMA_CHIP_ID_BCM43224: + case BCMA_CHIP_ID_BCM43225: + case BCMA_CHIP_ID_BCM43227: + case BCMA_CHIP_ID_BCM43228: + case BCMA_CHIP_ID_BCM4331: + case BCMA_CHIP_ID_BCM43421: + case BCMA_CHIP_ID_BCM43428: + case BCMA_CHIP_ID_BCM43431: case BCMA_CHIP_ID_BCM4716: - case BCMA_CHIP_ID_BCM4748: case BCMA_CHIP_ID_BCM47162: - case BCMA_CHIP_ID_BCM4313: - case BCMA_CHIP_ID_BCM5357: + case BCMA_CHIP_ID_BCM4748: case BCMA_CHIP_ID_BCM4749: + case BCMA_CHIP_ID_BCM5357: case BCMA_CHIP_ID_BCM53572: + case BCMA_CHIP_ID_BCM6362: /* always 20Mhz */ return 20000 * 1000; - case BCMA_CHIP_ID_BCM5356: case BCMA_CHIP_ID_BCM4706: + case BCMA_CHIP_ID_BCM5356: /* always 25Mhz */ return 25000 * 1000; + case BCMA_CHIP_ID_BCM43460: + case BCMA_CHIP_ID_BCM4352: + case BCMA_CHIP_ID_BCM4360: + if (cc->status & BCMA_CC_CHIPST_4360_XTAL_40MZ) + return 40000 * 1000; + else + return 20000 * 1000; default: bcma_warn(bus, "No ALP clock specified for %04X device, pmu rev. %d, using default %d Hz\n", bus->chipinfo.id, cc->pmu.rev, BCMA_CC_PMU_ALP_CLOCK); diff --git a/include/linux/bcma/bcma_driver_chipcommon.h b/include/linux/bcma/bcma_driver_chipcommon.h index 8390c474f69a..1db4c6de372e 100644 --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -104,6 +104,7 @@ #define BCMA_CC_CHIPST_4706_MIPS_BENDIAN BIT(3) /* 0: little, 1: big endian */ #define BCMA_CC_CHIPST_4706_PCIE1_DISABLE BIT(5) /* PCIE1 enable strap pin */ #define BCMA_CC_CHIPST_5357_NAND_BOOT BIT(4) /* NAND boot, valid for CC rev 38 and/or BCM5357 */ +#define BCMA_CC_CHIPST_4360_XTAL_40MZ 0x00000001 #define BCMA_CC_JCMD 0x0030 /* Rev >= 10 only */ #define BCMA_CC_JCMD_START 0x80000000 #define BCMA_CC_JCMD_BUSY 0x80000000 -- cgit v1.2.3 From 6951618b4b0bb022429ab17d49f2fa3650f21cb4 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Wed, 27 Mar 2013 17:23:11 +0100 Subject: bcma: export bcma_chipco_get_alp_clock() This function will be used by brcmsmac. Signed-off-by: Hauke Mehrtens Signed-off-by: John W. Linville --- drivers/bcma/driver_chipcommon.c | 3 ++- include/linux/bcma/bcma_driver_chipcommon.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/bcma/driver_chipcommon.c b/drivers/bcma/driver_chipcommon.c index 28fa50ad87be..88db0cb7bf19 100644 --- a/drivers/bcma/driver_chipcommon.c +++ b/drivers/bcma/driver_chipcommon.c @@ -25,13 +25,14 @@ static inline u32 bcma_cc_write32_masked(struct bcma_drv_cc *cc, u16 offset, return value; } -static u32 bcma_chipco_get_alp_clock(struct bcma_drv_cc *cc) +u32 bcma_chipco_get_alp_clock(struct bcma_drv_cc *cc) { if (cc->capabilities & BCMA_CC_CAP_PMU) return bcma_pmu_get_alp_clock(cc); return 20000000; } +EXPORT_SYMBOL_GPL(bcma_chipco_get_alp_clock); static u32 bcma_chipco_watchdog_get_max_timer(struct bcma_drv_cc *cc) { diff --git a/include/linux/bcma/bcma_driver_chipcommon.h b/include/linux/bcma/bcma_driver_chipcommon.h index 1db4c6de372e..453fcc914683 100644 --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -608,6 +608,8 @@ void bcma_chipco_bcm4331_ext_pa_lines_ctl(struct bcma_drv_cc *cc, bool enable); extern u32 bcma_chipco_watchdog_timer_set(struct bcma_drv_cc *cc, u32 ticks); +extern u32 bcma_chipco_get_alp_clock(struct bcma_drv_cc *cc); + void bcma_chipco_irq_mask(struct bcma_drv_cc *cc, u32 mask, u32 value); u32 bcma_chipco_irq_status(struct bcma_drv_cc *cc, u32 mask); -- cgit v1.2.3 From 1640f28f6b839637d9b82a3c4a19120601e59c66 Mon Sep 17 00:00:00 2001 From: Franky Lin Date: Thu, 11 Apr 2013 13:28:51 +0200 Subject: brcmfmac: add support for dongle ARM CR4 core Newer WiFi chip use ARM CR4 core to achieve higher performance. Add necessary code for host driver in order to support CR4 core. Reviewed-by: Arend van Spriel Reviewed-by: Pieter-Paul Giesberts Signed-off-by: Franky Lin Signed-off-by: Arend van Spriel Signed-off-by: John W. Linville --- drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c | 15 ++- .../net/wireless/brcm80211/brcmfmac/sdio_chip.c | 150 ++++++++++++++++++--- .../net/wireless/brcm80211/brcmfmac/sdio_chip.h | 6 +- include/linux/bcma/bcma.h | 1 + include/linux/bcma/bcma_regs.h | 1 + 5 files changed, 148 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c index 3147960ad975..d24eb66d324d 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@ -2652,7 +2652,7 @@ static int brcmf_sdio_readshared(struct brcmf_sdio *bus, struct sdpcm_shared_le sh_le; __le32 addr_le; - shaddr = bus->ramsize - 4; + shaddr = bus->ci->rambase + bus->ramsize - 4; /* * Read last word in socram to determine @@ -3030,10 +3030,11 @@ static int brcmf_sdbrcm_get_image(char *buf, int len, struct brcmf_sdio *bus) static int brcmf_sdbrcm_download_code_file(struct brcmf_sdio *bus) { - int offset = 0; + int offset; uint len; u8 *memblock = NULL, *memptr; int ret; + u8 idx; brcmf_dbg(INFO, "Enter\n"); @@ -3054,9 +3055,14 @@ static int brcmf_sdbrcm_download_code_file(struct brcmf_sdio *bus) memptr += (BRCMF_SDALIGN - ((u32)(unsigned long)memblock % BRCMF_SDALIGN)); + offset = bus->ci->rambase; + /* Download image */ - while ((len = - brcmf_sdbrcm_get_image((char *)memptr, MEMBLOCK, bus))) { + len = brcmf_sdbrcm_get_image((char *)memptr, MEMBLOCK, bus); + idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_ARM_CR4); + if (BRCMF_MAX_CORENUM != idx) + memcpy(&bus->ci->rst_vec, memptr, sizeof(bus->ci->rst_vec)); + while (len) { ret = brcmf_sdio_ramrw(bus->sdiodev, true, offset, memptr, len); if (ret) { brcmf_err("error %d on writing %d membytes at 0x%08x\n", @@ -3065,6 +3071,7 @@ static int brcmf_sdbrcm_download_code_file(struct brcmf_sdio *bus) } offset += MEMBLOCK; + len = brcmf_sdbrcm_get_image((char *)memptr, MEMBLOCK, bus); } err: diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c index 9818598f30ea..5db985cb0e0a 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c @@ -52,6 +52,9 @@ #define CIB_REV_MASK 0xff000000 #define CIB_REV_SHIFT 24 +/* ARM CR4 core specific control flag bits */ +#define ARMCR4_BCMA_IOCTL_CPUHALT 0x0020 + #define SDIOD_DRVSTR_KEY(chip, pmu) (((chip) << 16) | (pmu)) /* SDIO Pad drive strength to select value mappings */ struct sdiod_drive_str { @@ -149,7 +152,7 @@ brcmf_sdio_ai_iscoreup(struct brcmf_sdio_dev *sdiodev, static void brcmf_sdio_sb_coredisable(struct brcmf_sdio_dev *sdiodev, - struct chip_info *ci, u16 coreid) + struct chip_info *ci, u16 coreid, u32 core_bits) { u32 regdata, base; u8 idx; @@ -235,7 +238,7 @@ brcmf_sdio_sb_coredisable(struct brcmf_sdio_dev *sdiodev, static void brcmf_sdio_ai_coredisable(struct brcmf_sdio_dev *sdiodev, - struct chip_info *ci, u16 coreid) + struct chip_info *ci, u16 coreid, u32 core_bits) { u8 idx; u32 regdata; @@ -249,19 +252,36 @@ brcmf_sdio_ai_coredisable(struct brcmf_sdio_dev *sdiodev, if ((regdata & BCMA_RESET_CTL_RESET) != 0) return; - brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, 0, NULL); - regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + /* ensure no pending backplane operation + * 300uc should be sufficient for backplane ops to be finish + * extra 10ms is taken into account for firmware load stage + * after 10300us carry on disabling the core anyway + */ + SPINWAIT(brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_ST, + NULL), 10300); + regdata = brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_ST, NULL); - udelay(10); + if (regdata) + brcmf_err("disabling core 0x%x with reset status %x\n", + coreid, regdata); brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, BCMA_RESET_CTL_RESET, NULL); udelay(1); + + brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + core_bits, NULL); + regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + NULL); + usleep_range(10, 20); + } static void brcmf_sdio_sb_resetcore(struct brcmf_sdio_dev *sdiodev, - struct chip_info *ci, u16 coreid) + struct chip_info *ci, u16 coreid, u32 core_bits) { u32 regdata; u8 idx; @@ -272,7 +292,7 @@ brcmf_sdio_sb_resetcore(struct brcmf_sdio_dev *sdiodev, * Must do the disable sequence first to work for * arbitrary current core state. */ - brcmf_sdio_sb_coredisable(sdiodev, ci, coreid); + brcmf_sdio_sb_coredisable(sdiodev, ci, coreid, 0); /* * Now do the initialization sequence. @@ -325,7 +345,7 @@ brcmf_sdio_sb_resetcore(struct brcmf_sdio_dev *sdiodev, static void brcmf_sdio_ai_resetcore(struct brcmf_sdio_dev *sdiodev, - struct chip_info *ci, u16 coreid) + struct chip_info *ci, u16 coreid, u32 core_bits) { u8 idx; u32 regdata; @@ -333,28 +353,67 @@ brcmf_sdio_ai_resetcore(struct brcmf_sdio_dev *sdiodev, idx = brcmf_sdio_chip_getinfidx(ci, coreid); /* must disable first to work for arbitrary current core state */ - brcmf_sdio_ai_coredisable(sdiodev, ci, coreid); + brcmf_sdio_ai_coredisable(sdiodev, ci, coreid, core_bits); /* now do initialization sequence */ brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, - BCMA_IOCTL_FGC | BCMA_IOCTL_CLK, NULL); + core_bits | BCMA_IOCTL_FGC | BCMA_IOCTL_CLK, NULL); regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, NULL); brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, 0, NULL); + regdata = brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, + NULL); udelay(1); brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, - BCMA_IOCTL_CLK, NULL); + core_bits | BCMA_IOCTL_CLK, NULL); regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, NULL); udelay(1); } +#ifdef DEBUG +/* safety check for chipinfo */ +static int brcmf_sdio_chip_cichk(struct chip_info *ci) +{ + u8 core_idx; + + /* check RAM core presence for ARM CM3 core */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CM3); + if (BRCMF_MAX_CORENUM != core_idx) { + core_idx = brcmf_sdio_chip_getinfidx(ci, + BCMA_CORE_INTERNAL_MEM); + if (BRCMF_MAX_CORENUM == core_idx) { + brcmf_err("RAM core not provided with ARM CM3 core\n"); + return -ENODEV; + } + } + + /* check RAM base for ARM CR4 core */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CR4); + if (BRCMF_MAX_CORENUM != core_idx) { + if (ci->rambase == 0) { + brcmf_err("RAM base not provided with ARM CR4 core\n"); + return -ENOMEM; + } + } + + return 0; +} +#else /* DEBUG */ +static inline int brcmf_sdio_chip_cichk(struct chip_info *ci) +{ + return 0; +} +#endif + static int brcmf_sdio_chip_recognition(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, u32 regs) { u32 regdata; + int ret; /* Get CC core rev * Chipid is assume to be at offset 0 from regs arg @@ -439,6 +498,10 @@ static int brcmf_sdio_chip_recognition(struct brcmf_sdio_dev *sdiodev, return -ENODEV; } + ret = brcmf_sdio_chip_cichk(ci); + if (ret) + return ret; + switch (ci->socitype) { case SOCI_SB: ci->iscoreup = brcmf_sdio_sb_iscoreup; @@ -538,7 +601,7 @@ brcmf_sdio_chip_buscoresetup(struct brcmf_sdio_dev *sdiodev, * Make sure any on-chip ARM is off (in case strapping is wrong), * or downloaded code was already running. */ - ci->coredisable(sdiodev, ci, BCMA_CORE_ARM_CM3); + ci->coredisable(sdiodev, ci, BCMA_CORE_ARM_CM3, 0); } int brcmf_sdio_chip_attach(struct brcmf_sdio_dev *sdiodev, @@ -701,7 +764,7 @@ static bool brcmf_sdio_chip_writenvram(struct brcmf_sdio_dev *sdiodev, u32 token; __le32 token_le; - nvram_addr = (ci->ramsize - 4) - nvram_sz; + nvram_addr = (ci->ramsize - 4) - nvram_sz + ci->rambase; /* Write the vars list */ err = brcmf_sdio_ramrw(sdiodev, true, nvram_addr, nvram_dat, nvram_sz); @@ -728,7 +791,7 @@ static bool brcmf_sdio_chip_writenvram(struct brcmf_sdio_dev *sdiodev, nvram_addr, nvram_sz, token); /* Write the length token to the last word */ - if (brcmf_sdio_ramrw(sdiodev, true, (ci->ramsize - 4), + if (brcmf_sdio_ramrw(sdiodev, true, (ci->ramsize - 4 + ci->rambase), (u8 *)&token_le, 4)) return false; @@ -741,8 +804,8 @@ brcmf_sdio_chip_cm3_enterdl(struct brcmf_sdio_dev *sdiodev, { u32 zeros = 0; - ci->coredisable(sdiodev, ci, BCMA_CORE_ARM_CM3); - ci->resetcore(sdiodev, ci, BCMA_CORE_INTERNAL_MEM); + ci->coredisable(sdiodev, ci, BCMA_CORE_ARM_CM3, 0); + ci->resetcore(sdiodev, ci, BCMA_CORE_INTERNAL_MEM, 0); /* clear length token */ brcmf_sdio_ramrw(sdiodev, true, ci->ramsize - 4, (u8 *)&zeros, 4); @@ -769,7 +832,41 @@ brcmf_sdio_chip_cm3_exitdl(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, reg_addr += offsetof(struct sdpcmd_regs, intstatus); brcmf_sdio_regwl(sdiodev, reg_addr, 0xFFFFFFFF, NULL); - ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CM3); + ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CM3, 0); + + return true; +} + +static inline void +brcmf_sdio_chip_cr4_enterdl(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci) +{ + ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CR4, + ARMCR4_BCMA_IOCTL_CPUHALT); +} + +static bool +brcmf_sdio_chip_cr4_exitdl(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, + char *nvram_dat, uint nvram_sz) +{ + u8 core_idx; + u32 reg_addr; + + if (!brcmf_sdio_chip_writenvram(sdiodev, ci, nvram_dat, nvram_sz)) + return false; + + /* clear all interrupts */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_SDIO_DEV); + reg_addr = ci->c_inf[core_idx].base; + reg_addr += offsetof(struct sdpcmd_regs, intstatus); + brcmf_sdio_regwl(sdiodev, reg_addr, 0xFFFFFFFF, NULL); + + /* Write reset vector to address 0 */ + brcmf_sdio_ramrw(sdiodev, true, 0, (void *)&ci->rst_vec, + sizeof(ci->rst_vec)); + + /* restore ARM */ + ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CR4, 0); return true; } @@ -777,12 +874,27 @@ brcmf_sdio_chip_cm3_exitdl(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, void brcmf_sdio_chip_enter_download(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci) { - brcmf_sdio_chip_cm3_enterdl(sdiodev, ci); + u8 arm_core_idx; + + arm_core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CM3); + if (BRCMF_MAX_CORENUM != arm_core_idx) { + brcmf_sdio_chip_cm3_enterdl(sdiodev, ci); + return; + } + + brcmf_sdio_chip_cr4_enterdl(sdiodev, ci); } bool brcmf_sdio_chip_exit_download(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, char *nvram_dat, uint nvram_sz) { - return brcmf_sdio_chip_cm3_exitdl(sdiodev, ci, nvram_dat, nvram_sz); + u8 arm_core_idx; + + arm_core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CM3); + if (BRCMF_MAX_CORENUM != arm_core_idx) + return brcmf_sdio_chip_cm3_exitdl(sdiodev, ci, nvram_dat, + nvram_sz); + + return brcmf_sdio_chip_cr4_exitdl(sdiodev, ci, nvram_dat, nvram_sz); } diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h index 2123ea71d127..83c041f1bf4a 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h @@ -73,15 +73,17 @@ struct chip_info { u32 pmurev; u32 pmucaps; u32 ramsize; + u32 rambase; + u32 rst_vec; /* reset vertor for ARM CR4 core */ bool (*iscoreup)(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, u16 coreid); u32 (*corerev)(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, u16 coreid); void (*coredisable)(struct brcmf_sdio_dev *sdiodev, - struct chip_info *ci, u16 coreid); + struct chip_info *ci, u16 coreid, u32 core_bits); void (*resetcore)(struct brcmf_sdio_dev *sdiodev, - struct chip_info *ci, u16 coreid); + struct chip_info *ci, u16 coreid, u32 core_bits); }; struct sbconfig { diff --git a/include/linux/bcma/bcma.h b/include/linux/bcma/bcma.h index 0ab6712fd76b..f14a98a79c9d 100644 --- a/include/linux/bcma/bcma.h +++ b/include/linux/bcma/bcma.h @@ -134,6 +134,7 @@ struct bcma_host_ops { #define BCMA_CORE_I2S 0x834 #define BCMA_CORE_SDR_DDR1_MEM_CTL 0x835 /* SDR/DDR1 memory controller core */ #define BCMA_CORE_SHIM 0x837 /* SHIM component in ubus/6362 */ +#define BCMA_CORE_ARM_CR4 0x83e #define BCMA_CORE_DEFAULT 0xFFF #define BCMA_MAX_NR_CORES 16 diff --git a/include/linux/bcma/bcma_regs.h b/include/linux/bcma/bcma_regs.h index 7e8104bb7a7e..917dcd7965e7 100644 --- a/include/linux/bcma/bcma_regs.h +++ b/include/linux/bcma/bcma_regs.h @@ -37,6 +37,7 @@ #define BCMA_IOST_BIST_DONE 0x8000 #define BCMA_RESET_CTL 0x0800 #define BCMA_RESET_CTL_RESET 0x0001 +#define BCMA_RESET_ST 0x0804 /* BCMA PCI config space registers. */ #define BCMA_PCI_PMCSR 0x44 -- cgit v1.2.3 From 1e9ab4dd258ecbb0f1c377fd4dbe227cdb93d9bd Mon Sep 17 00:00:00 2001 From: Piotr Haber Date: Thu, 11 Apr 2013 13:28:52 +0200 Subject: brcmfmac: setup SDIO reset behavior Set device in a manner that SDIO I/O card reset will lead to WLAN backplane and PMU state reset. Reviewed-by: Hante Meuleman Reviewed-by: Arend van Spriel Reviewed-by: Pieter-Paul Giesberts Reviewed-by: Franky (Zhenhui) Lin Signed-off-by: Piotr Haber Signed-off-by: Arend van Spriel Signed-off-by: John W. Linville --- drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c | 38 ++++++++++++++++++---- .../net/wireless/brcm80211/brcmfmac/sdio_host.h | 2 ++ include/linux/bcma/bcma_driver_chipcommon.h | 3 ++ 3 files changed, 36 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c index d24eb66d324d..c06bb083c459 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@ -3635,7 +3635,6 @@ brcmf_sdbrcm_probe_attach(struct brcmf_sdio *bus, u32 regsva) int err = 0; int reg_addr; u32 reg_val; - u8 idx; bus->alp_only = true; @@ -3686,12 +3685,37 @@ brcmf_sdbrcm_probe_attach(struct brcmf_sdio *bus, u32 regsva) goto fail; } - /* Set core control so an SDIO reset does a backplane reset */ - idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_SDIO_DEV); - reg_addr = bus->ci->c_inf[idx].base + - offsetof(struct sdpcmd_regs, corecontrol); - reg_val = brcmf_sdio_regrl(bus->sdiodev, reg_addr, NULL); - brcmf_sdio_regwl(bus->sdiodev, reg_addr, reg_val | CC_BPRESEN, NULL); + /* Set card control so an SDIO card reset does a WLAN backplane reset */ + reg_val = brcmf_sdio_regrb(bus->sdiodev, + SDIO_CCCR_BRCM_CARDCTRL, &err); + if (err) + goto fail; + + reg_val |= SDIO_CCCR_BRCM_CARDCTRL_WLANRESET; + + brcmf_sdio_regwb(bus->sdiodev, + SDIO_CCCR_BRCM_CARDCTRL, reg_val, &err); + if (err) + goto fail; + + /* set PMUControl so a backplane reset does PMU state reload */ + reg_addr = CORE_CC_REG(bus->ci->c_inf[0].base, + pmucontrol); + reg_val = brcmf_sdio_regrl(bus->sdiodev, + reg_addr, + &err); + if (err) + goto fail; + + reg_val |= (BCMA_CC_PMU_CTL_RES_RELOAD << BCMA_CC_PMU_CTL_RES_SHIFT); + + brcmf_sdio_regwl(bus->sdiodev, + reg_addr, + reg_val, + &err); + if (err) + goto fail; + sdio_release_host(bus->sdiodev->func[1]); diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h index b9b397b59965..28ed3cc9f35a 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h @@ -52,6 +52,8 @@ #define SDIO_CCCR_BRCM_CARDCAP_CMD14_SUPPORT 0x02 #define SDIO_CCCR_BRCM_CARDCAP_CMD14_EXT 0x04 #define SDIO_CCCR_BRCM_CARDCAP_CMD_NODEC 0x08 +#define SDIO_CCCR_BRCM_CARDCTRL 0xf1 +#define SDIO_CCCR_BRCM_CARDCTRL_WLANRESET 0x02 #define SDIO_CCCR_BRCM_SEPINT 0xf2 #define SDIO_SEPINT_MASK 0x01 diff --git a/include/linux/bcma/bcma_driver_chipcommon.h b/include/linux/bcma/bcma_driver_chipcommon.h index 453fcc914683..b8b09eac60a4 100644 --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -316,6 +316,9 @@ #define BCMA_CC_PMU_CTL 0x0600 /* PMU control */ #define BCMA_CC_PMU_CTL_ILP_DIV 0xFFFF0000 /* ILP div mask */ #define BCMA_CC_PMU_CTL_ILP_DIV_SHIFT 16 +#define BCMA_CC_PMU_CTL_RES 0x00006000 /* reset control mask */ +#define BCMA_CC_PMU_CTL_RES_SHIFT 13 +#define BCMA_CC_PMU_CTL_RES_RELOAD 0x2 /* reload POR values */ #define BCMA_CC_PMU_CTL_PLL_UPD 0x00000400 #define BCMA_CC_PMU_CTL_NOILPONW 0x00000200 /* No ILP on wait */ #define BCMA_CC_PMU_CTL_HTREQEN 0x00000100 /* HT req enable */ -- cgit v1.2.3 From 668761ac01d6f5a36b8e5a24d4e154550e2c4c3b Mon Sep 17 00:00:00 2001 From: Hante Meuleman Date: Fri, 12 Apr 2013 10:55:55 +0200 Subject: brcmfmac: define and use platform specific data for SDIO. This patch adds support for platform specific data for SDIO fullmac devices. Currently OOB interrupts are configured by Kconfig BRCMFMAC_SDIO_OOB but that is now determined dynamically by checking availibility of platform data. Cc: Hauke Mehrtens Reviewed-by: Arend Van Spriel Reviewed-by: Franky (Zhenhui) Lin Reviewed-by: Pieter-Paul Giesberts Reviewed-by: Piotr Haber Signed-off-by: Hante Meuleman Signed-off-by: Arend van Spriel Signed-off-by: John W. Linville --- drivers/net/wireless/brcm80211/Kconfig | 9 -- drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c | 155 +++++++++++---------- .../net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c | 114 +++++---------- drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c | 29 ++-- .../net/wireless/brcm80211/brcmfmac/sdio_host.h | 6 +- include/linux/platform_data/brcmfmac-sdio.h | 124 +++++++++++++++++ 6 files changed, 250 insertions(+), 187 deletions(-) create mode 100644 include/linux/platform_data/brcmfmac-sdio.h (limited to 'include') diff --git a/drivers/net/wireless/brcm80211/Kconfig b/drivers/net/wireless/brcm80211/Kconfig index 747e9317dabd..fc8a0fa6d3b2 100644 --- a/drivers/net/wireless/brcm80211/Kconfig +++ b/drivers/net/wireless/brcm80211/Kconfig @@ -37,15 +37,6 @@ config BRCMFMAC_SDIO IEEE802.11n embedded FullMAC WLAN driver. Say Y if you want to use the driver for a SDIO wireless card. -config BRCMFMAC_SDIO_OOB - bool "Out of band interrupt support for SDIO interface chipset" - depends on BRCMFMAC_SDIO - ---help--- - This option enables out-of-band interrupt support for Broadcom - SDIO Wifi chipset using fullmac in order to gain better - performance and deep sleep wake up capability on certain - platforms. Say N if you are unsure. - config BRCMFMAC_USB bool "USB bus interface support for FullMAC driver" depends on USB diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c index aa51f370be49..4891e3df2058 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -37,16 +38,15 @@ #define SDIOH_API_ACCESS_RETRY_LIMIT 2 -#ifdef CONFIG_BRCMFMAC_SDIO_OOB -static irqreturn_t brcmf_sdio_irqhandler(int irq, void *dev_id) + +static irqreturn_t brcmf_sdio_oob_irqhandler(int irq, void *dev_id) { struct brcmf_bus *bus_if = dev_get_drvdata(dev_id); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; - brcmf_dbg(INTR, "oob intr triggered\n"); + brcmf_dbg(INTR, "OOB intr triggered\n"); - /* - * out-of-band interrupt is level-triggered which won't + /* out-of-band interrupt is level-triggered which won't * be cleared until dpc */ if (sdiodev->irq_en) { @@ -59,72 +59,12 @@ static irqreturn_t brcmf_sdio_irqhandler(int irq, void *dev_id) return IRQ_HANDLED; } -int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev) -{ - int ret = 0; - u8 data; - unsigned long flags; - - brcmf_dbg(SDIO, "Entering: irq %d\n", sdiodev->irq); - - ret = request_irq(sdiodev->irq, brcmf_sdio_irqhandler, - sdiodev->irq_flags, "brcmf_oob_intr", - &sdiodev->func[1]->dev); - if (ret != 0) - return ret; - spin_lock_init(&sdiodev->irq_en_lock); - spin_lock_irqsave(&sdiodev->irq_en_lock, flags); - sdiodev->irq_en = true; - spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags); - - ret = enable_irq_wake(sdiodev->irq); - if (ret != 0) - return ret; - sdiodev->irq_wake = true; - - sdio_claim_host(sdiodev->func[1]); - - /* must configure SDIO_CCCR_IENx to enable irq */ - data = brcmf_sdio_regrb(sdiodev, SDIO_CCCR_IENx, &ret); - data |= 1 << SDIO_FUNC_1 | 1 << SDIO_FUNC_2 | 1; - brcmf_sdio_regwb(sdiodev, SDIO_CCCR_IENx, data, &ret); - - /* redirect, configure and enable io for interrupt signal */ - data = SDIO_SEPINT_MASK | SDIO_SEPINT_OE; - if (sdiodev->irq_flags & IRQF_TRIGGER_HIGH) - data |= SDIO_SEPINT_ACT_HI; - brcmf_sdio_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, data, &ret); - - sdio_release_host(sdiodev->func[1]); - - return 0; -} - -int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev) -{ - brcmf_dbg(SDIO, "Entering\n"); - - sdio_claim_host(sdiodev->func[1]); - brcmf_sdio_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, 0, NULL); - brcmf_sdio_regwb(sdiodev, SDIO_CCCR_IENx, 0, NULL); - sdio_release_host(sdiodev->func[1]); - - if (sdiodev->irq_wake) { - disable_irq_wake(sdiodev->irq); - sdiodev->irq_wake = false; - } - free_irq(sdiodev->irq, &sdiodev->func[1]->dev); - sdiodev->irq_en = false; - - return 0; -} -#else /* CONFIG_BRCMFMAC_SDIO_OOB */ -static void brcmf_sdio_irqhandler(struct sdio_func *func) +static void brcmf_sdio_ib_irqhandler(struct sdio_func *func) { struct brcmf_bus *bus_if = dev_get_drvdata(&func->dev); struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; - brcmf_dbg(INTR, "ib intr triggered\n"); + brcmf_dbg(INTR, "IB intr triggered\n"); brcmf_sdbrcm_isr(sdiodev->bus); } @@ -136,12 +76,56 @@ static void brcmf_sdio_dummy_irqhandler(struct sdio_func *func) int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev) { - brcmf_dbg(SDIO, "Entering\n"); + int ret = 0; + u8 data; + unsigned long flags; - sdio_claim_host(sdiodev->func[1]); - sdio_claim_irq(sdiodev->func[1], brcmf_sdio_irqhandler); - sdio_claim_irq(sdiodev->func[2], brcmf_sdio_dummy_irqhandler); - sdio_release_host(sdiodev->func[1]); + if ((sdiodev->pdata) && (sdiodev->pdata->oob_irq_supported)) { + brcmf_dbg(SDIO, "Enter, register OOB IRQ %d\n", + sdiodev->pdata->oob_irq_nr); + ret = request_irq(sdiodev->pdata->oob_irq_nr, + brcmf_sdio_oob_irqhandler, + sdiodev->pdata->oob_irq_flags, + "brcmf_oob_intr", + &sdiodev->func[1]->dev); + if (ret != 0) { + brcmf_err("request_irq failed %d\n", ret); + return ret; + } + sdiodev->oob_irq_requested = true; + spin_lock_init(&sdiodev->irq_en_lock); + spin_lock_irqsave(&sdiodev->irq_en_lock, flags); + sdiodev->irq_en = true; + spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags); + + ret = enable_irq_wake(sdiodev->pdata->oob_irq_nr); + if (ret != 0) { + brcmf_err("enable_irq_wake failed %d\n", ret); + return ret; + } + sdiodev->irq_wake = true; + + sdio_claim_host(sdiodev->func[1]); + + /* must configure SDIO_CCCR_IENx to enable irq */ + data = brcmf_sdio_regrb(sdiodev, SDIO_CCCR_IENx, &ret); + data |= 1 << SDIO_FUNC_1 | 1 << SDIO_FUNC_2 | 1; + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_IENx, data, &ret); + + /* redirect, configure and enable io for interrupt signal */ + data = SDIO_SEPINT_MASK | SDIO_SEPINT_OE; + if (sdiodev->pdata->oob_irq_flags & IRQF_TRIGGER_HIGH) + data |= SDIO_SEPINT_ACT_HI; + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, data, &ret); + + sdio_release_host(sdiodev->func[1]); + } else { + brcmf_dbg(SDIO, "Entering\n"); + sdio_claim_host(sdiodev->func[1]); + sdio_claim_irq(sdiodev->func[1], brcmf_sdio_ib_irqhandler); + sdio_claim_irq(sdiodev->func[2], brcmf_sdio_dummy_irqhandler); + sdio_release_host(sdiodev->func[1]); + } return 0; } @@ -150,14 +134,31 @@ int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev) { brcmf_dbg(SDIO, "Entering\n"); - sdio_claim_host(sdiodev->func[1]); - sdio_release_irq(sdiodev->func[2]); - sdio_release_irq(sdiodev->func[1]); - sdio_release_host(sdiodev->func[1]); + if ((sdiodev->pdata) && (sdiodev->pdata->oob_irq_supported)) { + sdio_claim_host(sdiodev->func[1]); + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, 0, NULL); + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_IENx, 0, NULL); + sdio_release_host(sdiodev->func[1]); + + if (sdiodev->oob_irq_requested) { + sdiodev->oob_irq_requested = false; + if (sdiodev->irq_wake) { + disable_irq_wake(sdiodev->pdata->oob_irq_nr); + sdiodev->irq_wake = false; + } + free_irq(sdiodev->pdata->oob_irq_nr, + &sdiodev->func[1]->dev); + sdiodev->irq_en = false; + } + } else { + sdio_claim_host(sdiodev->func[1]); + sdio_release_irq(sdiodev->func[2]); + sdio_release_irq(sdiodev->func[1]); + sdio_release_host(sdiodev->func[1]); + } return 0; } -#endif /* CONFIG_BRCMFMAC_SDIO_OOB */ int brcmf_sdcard_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address) diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c index b1ea1036e825..44fa0cdbf97b 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c @@ -26,6 +26,7 @@ #include /* request_irq() */ #include #include +#include #include #include @@ -62,14 +63,8 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = { }; MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids); -#ifdef CONFIG_BRCMFMAC_SDIO_OOB -static struct list_head oobirq_lh; -struct brcmf_sdio_oobirq { - unsigned int irq; - unsigned long flags; - struct list_head list; -}; -#endif /* CONFIG_BRCMFMAC_SDIO_OOB */ +static struct brcmfmac_sdio_platform_data *brcmfmac_sdio_pdata; + static bool brcmf_pm_resume_error(struct brcmf_sdio_dev *sdiodev) @@ -428,33 +423,6 @@ void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev) } -#ifdef CONFIG_BRCMFMAC_SDIO_OOB -static int brcmf_sdio_getintrcfg(struct brcmf_sdio_dev *sdiodev) -{ - struct brcmf_sdio_oobirq *oobirq_entry; - - if (list_empty(&oobirq_lh)) { - brcmf_err("no valid oob irq resource\n"); - return -ENXIO; - } - - oobirq_entry = list_first_entry(&oobirq_lh, struct brcmf_sdio_oobirq, - list); - - sdiodev->irq = oobirq_entry->irq; - sdiodev->irq_flags = oobirq_entry->flags; - list_del(&oobirq_entry->list); - kfree(oobirq_entry); - - return 0; -} -#else -static inline int brcmf_sdio_getintrcfg(struct brcmf_sdio_dev *sdiodev) -{ - return 0; -} -#endif /* CONFIG_BRCMFMAC_SDIO_OOB */ - static int brcmf_ops_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { @@ -495,15 +463,13 @@ static int brcmf_ops_sdio_probe(struct sdio_func *func, dev_set_drvdata(&func->dev, bus_if); dev_set_drvdata(&sdiodev->func[1]->dev, bus_if); sdiodev->dev = &sdiodev->func[1]->dev; + sdiodev->pdata = brcmfmac_sdio_pdata; atomic_set(&sdiodev->suspend, false); init_waitqueue_head(&sdiodev->request_byte_wait); init_waitqueue_head(&sdiodev->request_word_wait); init_waitqueue_head(&sdiodev->request_chain_wait); init_waitqueue_head(&sdiodev->request_buffer_wait); - err = brcmf_sdio_getintrcfg(sdiodev); - if (err) - goto fail; brcmf_dbg(SDIO, "F2 found, calling brcmf_sdio_probe...\n"); err = brcmf_sdio_probe(sdiodev); @@ -598,7 +564,7 @@ static const struct dev_pm_ops brcmf_sdio_pm_ops = { static struct sdio_driver brcmf_sdmmc_driver = { .probe = brcmf_ops_sdio_probe, .remove = brcmf_ops_sdio_remove, - .name = "brcmfmac", + .name = BRCMFMAC_SDIO_PDATA_NAME, .id_table = brcmf_sdmmc_ids, #ifdef CONFIG_PM_SLEEP .drv = { @@ -607,72 +573,51 @@ static struct sdio_driver brcmf_sdmmc_driver = { #endif /* CONFIG_PM_SLEEP */ }; -#ifdef CONFIG_BRCMFMAC_SDIO_OOB static int brcmf_sdio_pd_probe(struct platform_device *pdev) { - struct resource *res; - struct brcmf_sdio_oobirq *oobirq_entry; - int i, ret; + int ret; - INIT_LIST_HEAD(&oobirq_lh); + brcmf_dbg(SDIO, "Enter\n"); - for (i = 0; ; i++) { - res = platform_get_resource(pdev, IORESOURCE_IRQ, i); - if (!res) - break; + brcmfmac_sdio_pdata = pdev->dev.platform_data; - oobirq_entry = kzalloc(sizeof(struct brcmf_sdio_oobirq), - GFP_KERNEL); - if (!oobirq_entry) - return -ENOMEM; - oobirq_entry->irq = res->start; - oobirq_entry->flags = res->flags & IRQF_TRIGGER_MASK; - list_add_tail(&oobirq_entry->list, &oobirq_lh); - } - if (i == 0) - return -ENXIO; + if (brcmfmac_sdio_pdata->power_on) + brcmfmac_sdio_pdata->power_on(); ret = sdio_register_driver(&brcmf_sdmmc_driver); - if (ret) brcmf_err("sdio_register_driver failed: %d\n", ret); return ret; } -static struct platform_driver brcmf_sdio_pd = { - .probe = brcmf_sdio_pd_probe, - .driver = { - .name = "brcmf_sdio_pd" - } -}; - -void brcmf_sdio_exit(void) +static int brcmf_sdio_pd_remove(struct platform_device *pdev) { brcmf_dbg(SDIO, "Enter\n"); + if (brcmfmac_sdio_pdata->power_off) + brcmfmac_sdio_pdata->power_off(); + sdio_unregister_driver(&brcmf_sdmmc_driver); - platform_driver_unregister(&brcmf_sdio_pd); + return 0; } -void brcmf_sdio_init(void) -{ - int ret; - - brcmf_dbg(SDIO, "Enter\n"); - - ret = platform_driver_register(&brcmf_sdio_pd); +static struct platform_driver brcmf_sdio_pd = { + .remove = brcmf_sdio_pd_remove, + .driver = { + .name = BRCMFMAC_SDIO_PDATA_NAME + } +}; - if (ret) - brcmf_err("platform_driver_register failed: %d\n", ret); -} -#else void brcmf_sdio_exit(void) { brcmf_dbg(SDIO, "Enter\n"); - sdio_unregister_driver(&brcmf_sdmmc_driver); + if (brcmfmac_sdio_pdata) + platform_driver_unregister(&brcmf_sdio_pd); + else + sdio_unregister_driver(&brcmf_sdmmc_driver); } void brcmf_sdio_init(void) @@ -681,9 +626,12 @@ void brcmf_sdio_init(void) brcmf_dbg(SDIO, "Enter\n"); - ret = sdio_register_driver(&brcmf_sdmmc_driver); + ret = platform_driver_probe(&brcmf_sdio_pd, brcmf_sdio_pd_probe); + if (ret == -ENODEV) { + brcmf_dbg(SDIO, "No platform data available, registering without.\n"); + ret = sdio_register_driver(&brcmf_sdmmc_driver); + } if (ret) - brcmf_err("sdio_register_driver failed: %d\n", ret); + brcmf_err("driver registration failed: %d\n", ret); } -#endif /* CONFIG_BRCMFMAC_SDIO_OOB */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c index fd697ce3683c..d2487518bd2a 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -517,7 +518,7 @@ static int qcount[NUMPRIO]; static int tx_packets[NUMPRIO]; #endif /* DEBUG */ -#define SDIO_DRIVE_STRENGTH 6 /* in milliamps */ +#define DEFAULT_SDIO_DRIVE_STRENGTH 6 /* in milliamps */ #define RETRYCHAN(chan) ((chan) == SDPCM_EVENT_CHANNEL) @@ -2046,23 +2047,19 @@ static void brcmf_sdbrcm_bus_stop(struct device *dev) bus->tx_seq = bus->rx_seq = 0; } -#ifdef CONFIG_BRCMFMAC_SDIO_OOB static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus) { unsigned long flags; - spin_lock_irqsave(&bus->sdiodev->irq_en_lock, flags); - if (!bus->sdiodev->irq_en && !atomic_read(&bus->ipend)) { - enable_irq(bus->sdiodev->irq); - bus->sdiodev->irq_en = true; + if (bus->sdiodev->oob_irq_requested) { + spin_lock_irqsave(&bus->sdiodev->irq_en_lock, flags); + if (!bus->sdiodev->irq_en && !atomic_read(&bus->ipend)) { + enable_irq(bus->sdiodev->pdata->oob_irq_nr); + bus->sdiodev->irq_en = true; + } + spin_unlock_irqrestore(&bus->sdiodev->irq_en_lock, flags); } - spin_unlock_irqrestore(&bus->sdiodev->irq_en_lock, flags); -} -#else -static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus) -{ } -#endif /* CONFIG_BRCMFMAC_SDIO_OOB */ static inline void brcmf_sdbrcm_adddpctsk(struct brcmf_sdio *bus) { @@ -3639,6 +3636,7 @@ brcmf_sdbrcm_probe_attach(struct brcmf_sdio *bus, u32 regsva) int err = 0; int reg_addr; u32 reg_val; + u32 drivestrength; bus->alp_only = true; @@ -3679,8 +3677,11 @@ brcmf_sdbrcm_probe_attach(struct brcmf_sdio *bus, u32 regsva) goto fail; } - brcmf_sdio_chip_drivestrengthinit(bus->sdiodev, bus->ci, - SDIO_DRIVE_STRENGTH); + if ((bus->sdiodev->pdata) && (bus->sdiodev->pdata->drive_strength)) + drivestrength = bus->sdiodev->pdata->drive_strength; + else + drivestrength = DEFAULT_SDIO_DRIVE_STRENGTH; + brcmf_sdio_chip_drivestrengthinit(bus->sdiodev, bus->ci, drivestrength); /* Get info on the SOCRAM cores... */ bus->ramsize = bus->ci->ramsize; diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h index 28ed3cc9f35a..7c1b6332747e 100644 --- a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h @@ -174,13 +174,11 @@ struct brcmf_sdio_dev { wait_queue_head_t request_buffer_wait; struct device *dev; struct brcmf_bus *bus_if; -#ifdef CONFIG_BRCMFMAC_SDIO_OOB - unsigned int irq; /* oob interrupt number */ - unsigned long irq_flags; /* board specific oob flags */ + struct brcmfmac_sdio_platform_data *pdata; + bool oob_irq_requested; bool irq_en; /* irq enable flags */ spinlock_t irq_en_lock; bool irq_wake; /* irq wake enable flags */ -#endif /* CONFIG_BRCMFMAC_SDIO_OOB */ }; /* Register/deregister interrupt handler. */ diff --git a/include/linux/platform_data/brcmfmac-sdio.h b/include/linux/platform_data/brcmfmac-sdio.h new file mode 100644 index 000000000000..1ade657d5fc1 --- /dev/null +++ b/include/linux/platform_data/brcmfmac-sdio.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2013 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_BRCMFMAC_PLATFORM_H +#define _LINUX_BRCMFMAC_PLATFORM_H + +/* + * Platform specific driver functions and data. Through the platform specific + * device data functions can be provided to help the brcmfmac driver to + * operate with the device in combination with the used platform. + * + * Use the platform data in the following (similar) way: + * + * +#include + + +static void brcmfmac_power_on(void) +{ +} + +static void brcmfmac_power_off(void) +{ +} + +static void brcmfmac_reset(void) +{ +} + +static struct brcmfmac_sdio_platform_data brcmfmac_sdio_pdata = { + .power_on = brcmfmac_power_on, + .power_off = brcmfmac_power_off, + .reset = brcmfmac_reset +}; + +static struct platform_device brcmfmac_device = { + .name = BRCMFMAC_SDIO_PDATA_NAME, + .id = PLATFORM_DEVID_NONE, + .dev.platform_data = &brcmfmac_sdio_pdata +}; + +void __init brcmfmac_init_pdata(void) +{ + brcmfmac_sdio_pdata.oob_irq_supported = true; + brcmfmac_sdio_pdata.oob_irq_nr = gpio_to_irq(GPIO_BRCMF_SDIO_OOB); + brcmfmac_sdio_pdata.oob_irq_flags = IORESOURCE_IRQ | + IORESOURCE_IRQ_HIGHLEVEL; + platform_device_register(&brcmfmac_device); +} + * + * + * Note: the brcmfmac can be loaded as module or be statically built-in into + * the kernel. If built-in then do note that it uses module_init (and + * module_exit) routines which equal device_initcall. So if you intend to + * create a module with the platform specific data for the brcmfmac and have + * it built-in to the kernel then use a higher initcall then device_initcall + * (see init.h). If this is not done then brcmfmac will load without problems + * but will not pickup the platform data. + * + * When the driver does not "detect" platform driver data then it will continue + * without reporting anything and just assume there is no data needed. Which is + * probably true for most platforms. + * + * Explanation of the platform_data fields: + * + * drive_strength: is the preferred drive_strength to be used for the SDIO + * pins. If 0 then a default value will be used. This is the target drive + * strength, the exact drive strength which will be used depends on the + * capabilities of the device. + * + * oob_irq_supported: does the board have support for OOB interrupts. SDIO + * in-band interrupts are relatively slow and for having less overhead on + * interrupt processing an out of band interrupt can be used. If the HW + * supports this then enable this by setting this field to true and configure + * the oob related fields. + * + * oob_irq_nr, oob_irq_flags: the OOB interrupt information. The values are + * used for registering the irq using request_irq function. + * + * power_on: This function is called by the brcmfmac when the module gets + * loaded. This can be particularly useful for low power devices. The platform + * spcific routine may for example decide to power up the complete device. + * If there is no use-case for this function then provide NULL. + * + * power_off: This function is called by the brcmfmac when the module gets + * unloaded. At this point the device can be powered down or otherwise be reset. + * So if an actual power_off is not supported but reset is then reset the device + * when this function gets called. This can be particularly useful for low power + * devices. If there is no use-case for this function (either power-down or + * reset) then provide NULL. + * + * reset: This function can get called if the device communication broke down. + * This functionality is particularly useful in case of SDIO type devices. It is + * possible to reset a dongle via sdio data interface, but it requires that + * this is fully functional. This function is chip/module specific and this + * function should return only after the complete reset has completed. + */ + +#define BRCMFMAC_SDIO_PDATA_NAME "brcmfmac_sdio" + +struct brcmfmac_sdio_platform_data { + unsigned int drive_strength; + bool oob_irq_supported; + unsigned int oob_irq_nr; + unsigned long oob_irq_flags; + void (*power_on)(void); + void (*power_off)(void); + void (*reset)(void); +}; + +#endif /* _LINUX_BRCMFMAC_PLATFORM_H */ -- cgit v1.2.3