]> Pileus Git - ~andy/linux/blobdiff - net/nfc/llcp/llcp.c
Merge tag 'perf-core-for-mingo' of git://git.kernel.org/pub/scm/linux/kernel/git...
[~andy/linux] / net / nfc / llcp / llcp.c
index 9ab17ec50ce764344fa2b3e62df7fb8147a59071..82f0f7588b463d8ba0a8e1931124c03f94de29ed 100644 (file)
@@ -123,14 +123,50 @@ static void local_release(struct kref *ref)
 
 int nfc_llcp_local_put(struct nfc_llcp_local *local)
 {
-       WARN_ON(local == NULL);
-
        if (local == NULL)
                return 0;
 
        return kref_put(&local->ref, local_release);
 }
 
+static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local,
+                                              u8 ssap, u8 dsap)
+{
+       struct sock *sk;
+       struct hlist_node *node;
+       struct nfc_llcp_sock *llcp_sock;
+
+       pr_debug("ssap dsap %d %d\n", ssap, dsap);
+
+       if (ssap == 0 && dsap == 0)
+               return NULL;
+
+       read_lock(&local->sockets.lock);
+
+       llcp_sock = NULL;
+
+       sk_for_each(sk, node, &local->sockets.head) {
+               llcp_sock = nfc_llcp_sock(sk);
+
+               if (llcp_sock->ssap == ssap && llcp_sock->dsap == dsap)
+                       break;
+       }
+
+       read_unlock(&local->sockets.lock);
+
+       if (llcp_sock == NULL)
+               return NULL;
+
+       sock_hold(&llcp_sock->sk);
+
+       return llcp_sock;
+}
+
+static void nfc_llcp_sock_put(struct nfc_llcp_sock *sock)
+{
+       sock_put(&sock->sk);
+}
+
 static void nfc_llcp_timeout_work(struct work_struct *work)
 {
        struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
@@ -191,6 +227,51 @@ static int nfc_llcp_wks_sap(char *service_name, size_t service_name_len)
        return -EINVAL;
 }
 
+static
+struct nfc_llcp_sock *nfc_llcp_sock_from_sn(struct nfc_llcp_local *local,
+                                           u8 *sn, size_t sn_len)
+{
+       struct sock *sk;
+       struct hlist_node *node;
+       struct nfc_llcp_sock *llcp_sock, *tmp_sock;
+
+       pr_debug("sn %zd %p\n", sn_len, sn);
+
+       if (sn == NULL || sn_len == 0)
+               return NULL;
+
+       read_lock(&local->sockets.lock);
+
+       llcp_sock = NULL;
+
+       sk_for_each(sk, node, &local->sockets.head) {
+               tmp_sock = nfc_llcp_sock(sk);
+
+               pr_debug("llcp sock %p\n", tmp_sock);
+
+               if (tmp_sock->sk.sk_state != LLCP_LISTEN)
+                       continue;
+
+               if (tmp_sock->service_name == NULL ||
+                   tmp_sock->service_name_len == 0)
+                       continue;
+
+               if (tmp_sock->service_name_len != sn_len)
+                       continue;
+
+               if (memcmp(sn, tmp_sock->service_name, sn_len) == 0) {
+                       llcp_sock = tmp_sock;
+                       break;
+               }
+       }
+
+       read_unlock(&local->sockets.lock);
+
+       pr_debug("Found llcp sock %p\n", llcp_sock);
+
+       return llcp_sock;
+}
+
 u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local,
                         struct nfc_llcp_sock *sock)
 {
@@ -217,22 +298,19 @@ u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local,
                }
 
                /*
-                * This is not a well known service,
-                * we should try to find a local SDP free spot
+                * Check if there already is a non WKS socket bound
+                * to this service name.
                 */
-               ssap = find_first_zero_bit(&local->local_sdp, LLCP_SDP_NUM_SAP);
-               if (ssap == LLCP_SDP_NUM_SAP) {
+               if (nfc_llcp_sock_from_sn(local, sock->service_name,
+                                         sock->service_name_len) != NULL) {
                        mutex_unlock(&local->sdp_lock);
 
                        return LLCP_SAP_MAX;
                }
 
-               pr_debug("SDP ssap %d\n", LLCP_WKS_NUM_SAP + ssap);
-
-               set_bit(ssap, &local->local_sdp);
                mutex_unlock(&local->sdp_lock);
 
-               return LLCP_WKS_NUM_SAP + ssap;
+               return LLCP_SDP_UNBOUND;
 
        } else if (sock->ssap != 0 && sock->ssap < LLCP_WKS_NUM_SAP) {
                if (!test_bit(sock->ssap, &local->local_wks)) {
@@ -276,8 +354,34 @@ void nfc_llcp_put_ssap(struct nfc_llcp_local *local, u8 ssap)
                local_ssap = ssap;
                sdp = &local->local_wks;
        } else if (ssap < LLCP_LOCAL_NUM_SAP) {
+               atomic_t *client_cnt;
+
                local_ssap = ssap - LLCP_WKS_NUM_SAP;
                sdp = &local->local_sdp;
+               client_cnt = &local->local_sdp_cnt[local_ssap];
+
+               pr_debug("%d clients\n", atomic_read(client_cnt));
+
+               mutex_lock(&local->sdp_lock);
+
+               if (atomic_dec_and_test(client_cnt)) {
+                       struct nfc_llcp_sock *l_sock;
+
+                       pr_debug("No more clients for SAP %d\n", ssap);
+
+                       clear_bit(local_ssap, sdp);
+
+                       /* Find the listening sock and set it back to UNBOUND */
+                       l_sock = nfc_llcp_sock_get(local, ssap, LLCP_SAP_SDP);
+                       if (l_sock) {
+                               l_sock->ssap = LLCP_SDP_UNBOUND;
+                               nfc_llcp_sock_put(l_sock);
+                       }
+               }
+
+               mutex_unlock(&local->sdp_lock);
+
+               return;
        } else if (ssap < LLCP_MAX_SAP) {
                local_ssap = ssap - LLCP_LOCAL_NUM_SAP;
                sdp = &local->local_sap;
@@ -292,6 +396,28 @@ void nfc_llcp_put_ssap(struct nfc_llcp_local *local, u8 ssap)
        mutex_unlock(&local->sdp_lock);
 }
 
+static u8 nfc_llcp_reserve_sdp_ssap(struct nfc_llcp_local *local)
+{
+       u8 ssap;
+
+       mutex_lock(&local->sdp_lock);
+
+       ssap = find_first_zero_bit(&local->local_sdp, LLCP_SDP_NUM_SAP);
+       if (ssap == LLCP_SDP_NUM_SAP) {
+               mutex_unlock(&local->sdp_lock);
+
+               return LLCP_SAP_MAX;
+       }
+
+       pr_debug("SDP ssap %d\n", LLCP_WKS_NUM_SAP + ssap);
+
+       set_bit(ssap, &local->local_sdp);
+
+       mutex_unlock(&local->sdp_lock);
+
+       return LLCP_WKS_NUM_SAP + ssap;
+}
+
 static int nfc_llcp_build_gb(struct nfc_llcp_local *local)
 {
        u8 *gb_cur, *version_tlv, version, version_length;
@@ -493,74 +619,12 @@ out:
        return llcp_sock;
 }
 
-static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local,
-                                              u8 ssap, u8 dsap)
-{
-       struct sock *sk;
-       struct hlist_node *node;
-       struct nfc_llcp_sock *llcp_sock;
-
-       pr_debug("ssap dsap %d %d\n", ssap, dsap);
-
-       if (ssap == 0 && dsap == 0)
-               return NULL;
-
-       read_lock(&local->sockets.lock);
-
-       llcp_sock = NULL;
-
-       sk_for_each(sk, node, &local->sockets.head) {
-               llcp_sock = nfc_llcp_sock(sk);
-
-               if (llcp_sock->ssap == ssap &&
-                   llcp_sock->dsap == dsap)
-                       break;
-       }
-
-       read_unlock(&local->sockets.lock);
-
-       if (llcp_sock == NULL)
-               return NULL;
-
-       sock_hold(&llcp_sock->sk);
-
-       return llcp_sock;
-}
-
 static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local,
                                                  u8 *sn, size_t sn_len)
 {
-       struct sock *sk;
-       struct hlist_node *node;
        struct nfc_llcp_sock *llcp_sock;
 
-       pr_debug("sn %zd\n", sn_len);
-
-       if (sn == NULL || sn_len == 0)
-               return NULL;
-
-       read_lock(&local->sockets.lock);
-
-       llcp_sock = NULL;
-
-       sk_for_each(sk, node, &local->sockets.head) {
-               llcp_sock = nfc_llcp_sock(sk);
-
-               if (llcp_sock->sk.sk_state != LLCP_LISTEN)
-                       continue;
-
-               if (llcp_sock->service_name == NULL ||
-                   llcp_sock->service_name_len == 0)
-                       continue;
-
-               if (llcp_sock->service_name_len != sn_len)
-                       continue;
-
-               if (memcmp(sn, llcp_sock->service_name, sn_len) == 0)
-                       break;
-       }
-
-       read_unlock(&local->sockets.lock);
+       llcp_sock = nfc_llcp_sock_from_sn(local, sn, sn_len);
 
        if (llcp_sock == NULL)
                return NULL;
@@ -570,11 +634,6 @@ static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local,
        return llcp_sock;
 }
 
-static void nfc_llcp_sock_put(struct nfc_llcp_sock *sock)
-{
-       sock_put(&sock->sk);
-}
-
 static u8 *nfc_llcp_connect_sn(struct sk_buff *skb, size_t *sn_len)
 {
        u8 *tlv = &skb->data[2], type, length;
@@ -646,6 +705,21 @@ static void nfc_llcp_recv_connect(struct nfc_llcp_local *local,
                goto fail;
        }
 
+       if (sock->ssap == LLCP_SDP_UNBOUND) {
+               u8 ssap = nfc_llcp_reserve_sdp_ssap(local);
+
+               pr_debug("First client, reserving %d\n", ssap);
+
+               if (ssap == LLCP_SAP_MAX) {
+                       reason = LLCP_DM_REJ;
+                       release_sock(&sock->sk);
+                       sock_put(&sock->sk);
+                       goto fail;
+               }
+
+               sock->ssap = ssap;
+       }
+
        new_sk = nfc_llcp_sock_alloc(NULL, parent->sk_type, GFP_ATOMIC);
        if (new_sk == NULL) {
                reason = LLCP_DM_REJ;
@@ -659,10 +733,21 @@ static void nfc_llcp_recv_connect(struct nfc_llcp_local *local,
        new_sock->local = nfc_llcp_local_get(local);
        new_sock->miu = local->remote_miu;
        new_sock->nfc_protocol = sock->nfc_protocol;
-       new_sock->ssap = sock->ssap;
        new_sock->dsap = ssap;
        new_sock->target_idx = local->target_idx;
        new_sock->parent = parent;
+       new_sock->ssap = sock->ssap;
+       if (sock->ssap < LLCP_LOCAL_NUM_SAP && sock->ssap >= LLCP_WKS_NUM_SAP) {
+               atomic_t *client_count;
+
+               pr_debug("reserved_ssap %d for %p\n", sock->ssap, new_sock);
+
+               client_count =
+                       &local->local_sdp_cnt[sock->ssap - LLCP_WKS_NUM_SAP];
+
+               atomic_inc(client_count);
+               new_sock->reserved_ssap = sock->ssap;
+       }
 
        nfc_llcp_parse_connection_tlv(new_sock, &skb->data[LLCP_HEADER_SIZE],
                                      skb->len - LLCP_HEADER_SIZE);
@@ -871,6 +956,45 @@ static void nfc_llcp_recv_cc(struct nfc_llcp_local *local, struct sk_buff *skb)
        nfc_llcp_sock_put(llcp_sock);
 }
 
+static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, struct sk_buff *skb)
+{
+       struct nfc_llcp_sock *llcp_sock;
+       struct sock *sk;
+       u8 dsap, ssap, reason;
+
+       dsap = nfc_llcp_dsap(skb);
+       ssap = nfc_llcp_ssap(skb);
+       reason = skb->data[2];
+
+       pr_debug("%d %d reason %d\n", ssap, dsap, reason);
+
+       switch (reason) {
+       case LLCP_DM_NOBOUND:
+       case LLCP_DM_REJ:
+               llcp_sock = nfc_llcp_connecting_sock_get(local, dsap);
+               break;
+
+       default:
+               llcp_sock = nfc_llcp_sock_get(local, dsap, ssap);
+               break;
+       }
+
+       if (llcp_sock == NULL) {
+               pr_err("Invalid DM\n");
+               return;
+       }
+
+       sk = &llcp_sock->sk;
+
+       sk->sk_err = ENXIO;
+       sk->sk_state = LLCP_CLOSED;
+       sk->sk_state_change(sk);
+
+       nfc_llcp_sock_put(llcp_sock);
+
+       return;
+}
+
 static void nfc_llcp_rx_work(struct work_struct *work)
 {
        struct nfc_llcp_local *local = container_of(work, struct nfc_llcp_local,
@@ -914,6 +1038,11 @@ static void nfc_llcp_rx_work(struct work_struct *work)
                nfc_llcp_recv_cc(local, skb);
                break;
 
+       case LLCP_PDU_DM:
+               pr_debug("DM\n");
+               nfc_llcp_recv_dm(local, skb);
+               break;
+
        case LLCP_PDU_I:
        case LLCP_PDU_RR:
        case LLCP_PDU_RNR: