Merge tag 'rtc-v4.4' of git://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux
[firefly-linux-kernel-4.4.55.git] / drivers / nfc / st-nci / se.c
index 2812884847942a1590a9ff9848068348cdfcefb1..dbab722a06546ab0c2dbbdba9d87e0fb12561132 100644 (file)
@@ -62,7 +62,7 @@ struct st_nci_pipe_info {
 
 #define ST_NCI_EVT_SE_HARD_RESET               0x20
 #define ST_NCI_EVT_TRANSMIT_DATA               0x10
-#define ST_NCI_EVT_WTX_REQUEST         0x11
+#define ST_NCI_EVT_WTX_REQUEST                 0x11
 #define ST_NCI_EVT_SE_SOFT_RESET               0x11
 #define ST_NCI_EVT_SE_END_OF_APDU_TRANSFER     0x21
 #define ST_NCI_EVT_HOT_PLUG                    0x03
@@ -419,15 +419,11 @@ void st_nci_hci_cmd_received(struct nci_dev *ndev, u8 pipe, u8 cmd,
 }
 EXPORT_SYMBOL_GPL(st_nci_hci_cmd_received);
 
-/*
- * Remarks: On some early st_nci firmware, nci_nfcee_mode_set(0)
- * is rejected
- */
 static int st_nci_control_se(struct nci_dev *ndev, u8 se_idx,
-                                  u8 state)
+                            u8 state)
 {
        struct st_nci_info *info = nci_get_drvdata(ndev);
-       int r;
+       int r, i;
        struct sk_buff *sk_host_list;
        u8 host_id;
 
@@ -449,7 +445,7 @@ static int st_nci_control_se(struct nci_dev *ndev, u8 se_idx,
         * retrieve a relevant host list.
         */
        reinit_completion(&info->se_info.req_completion);
-       r = nci_nfcee_mode_set(ndev, se_idx, NCI_NFCEE_ENABLE);
+       r = nci_nfcee_mode_set(ndev, se_idx, state);
        if (r != NCI_STATUS_OK)
                return r;
 
@@ -465,14 +461,19 @@ static int st_nci_control_se(struct nci_dev *ndev, u8 se_idx,
         * There is no possible synchronization to prevent this.
         * Adding a small delay is the only way to solve the issue.
         */
-       usleep_range(3000, 5000);
+       if (info->se_info.se_status->is_ese_present &&
+           info->se_info.se_status->is_uicc_present)
+               usleep_range(15000, 20000);
 
        r = nci_hci_get_param(ndev, NCI_HCI_ADMIN_GATE,
                        NCI_HCI_ADMIN_PARAM_HOST_LIST, &sk_host_list);
        if (r != NCI_HCI_ANY_OK)
                return r;
 
-       host_id = sk_host_list->data[sk_host_list->len - 1];
+       for (i = 0; i < sk_host_list->len &&
+               sk_host_list->data[i] != se_idx; i++)
+               ;
+       host_id = sk_host_list->data[i];
        kfree_skb(sk_host_list);
        if (state == ST_NCI_SE_MODE_ON && host_id == se_idx)
                return se_idx;
@@ -488,11 +489,20 @@ int st_nci_disable_se(struct nci_dev *ndev, u32 se_idx)
 
        pr_debug("st_nci_disable_se\n");
 
-       if (se_idx == NFC_SE_EMBEDDED) {
-               r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
-                               ST_NCI_EVT_SE_END_OF_APDU_TRANSFER, NULL, 0);
-               if (r < 0)
-                       return r;
+       /*
+        * According to upper layer, se_idx == NFC_SE_UICC when
+        * info->se_info.se_status->is_uicc_enable is true should never happen
+        * Same for eSE.
+        */
+       r = st_nci_control_se(ndev, se_idx, ST_NCI_SE_MODE_OFF);
+       if (r < 0) {
+               /* Do best effort to release SWP */
+               if (se_idx == NFC_SE_EMBEDDED) {
+                       r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
+                                       ST_NCI_EVT_SE_END_OF_APDU_TRANSFER,
+                                       NULL, 0);
+               }
+               return r;
        }
 
        return 0;
@@ -505,11 +515,25 @@ int st_nci_enable_se(struct nci_dev *ndev, u32 se_idx)
 
        pr_debug("st_nci_enable_se\n");
 
-       if (se_idx == ST_NCI_HCI_HOST_ID_ESE) {
+       /*
+        * According to upper layer, se_idx == NFC_SE_UICC when
+        * info->se_info.se_status->is_uicc_enable is true should never happen.
+        * Same for eSE.
+        */
+       r = st_nci_control_se(ndev, se_idx, ST_NCI_SE_MODE_ON);
+       if (r == ST_NCI_HCI_HOST_ID_ESE) {
+               st_nci_se_get_atr(ndev);
                r = nci_hci_send_event(ndev, ST_NCI_APDU_READER_GATE,
                                ST_NCI_EVT_SE_SOFT_RESET, NULL, 0);
-               if (r < 0)
-                       return r;
+       }
+
+       if (r < 0) {
+               /*
+                * The activation procedure failed, the secure element
+                * is not connected. Remove from the list.
+                */
+               nfc_remove_se(ndev->nfc_dev, se_idx);
+               return r;
        }
 
        return 0;
@@ -592,8 +616,8 @@ exit:
 
 int st_nci_discover_se(struct nci_dev *ndev)
 {
-       u8 param[2];
-       int r;
+       u8 white_list[2];
+       int r, wl_size = 0;
        int se_count = 0;
        struct st_nci_info *info = nci_get_drvdata(ndev);
 
@@ -606,29 +630,34 @@ int st_nci_discover_se(struct nci_dev *ndev)
        if (test_bit(ST_NCI_FACTORY_MODE, &info->flags))
                return 0;
 
-       param[0] = ST_NCI_UICC_HOST_ID;
-       param[1] = ST_NCI_HCI_HOST_ID_ESE;
-       r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE,
-                               NCI_HCI_ADMIN_PARAM_WHITELIST,
-                               param, sizeof(param));
-       if (r != NCI_HCI_ANY_OK)
-               return r;
+       if (info->se_info.se_status->is_ese_present &&
+           info->se_info.se_status->is_uicc_present) {
+               white_list[wl_size++] = ST_NCI_UICC_HOST_ID;
+               white_list[wl_size++] = ST_NCI_ESE_HOST_ID;
+       } else if (!info->se_info.se_status->is_ese_present &&
+                  info->se_info.se_status->is_uicc_present) {
+               white_list[wl_size++] = ST_NCI_UICC_HOST_ID;
+       } else if (info->se_info.se_status->is_ese_present &&
+                  !info->se_info.se_status->is_uicc_present) {
+               white_list[wl_size++] = ST_NCI_ESE_HOST_ID;
+       }
+
+       if (wl_size) {
+               r = nci_hci_set_param(ndev, NCI_HCI_ADMIN_GATE,
+                                     NCI_HCI_ADMIN_PARAM_WHITELIST,
+                                     white_list, wl_size);
+               if (r != NCI_HCI_ANY_OK)
+                       return r;
+       }
 
-       r = st_nci_control_se(ndev, ST_NCI_UICC_HOST_ID,
-                               ST_NCI_SE_MODE_ON);
-       if (r == ST_NCI_UICC_HOST_ID) {
+       if (info->se_info.se_status->is_uicc_present) {
                nfc_add_se(ndev->nfc_dev, ST_NCI_UICC_HOST_ID, NFC_SE_UICC);
                se_count++;
        }
 
-       /* Try to enable eSE in order to check availability */
-       r = st_nci_control_se(ndev, ST_NCI_HCI_HOST_ID_ESE,
-                               ST_NCI_SE_MODE_ON);
-       if (r == ST_NCI_HCI_HOST_ID_ESE) {
-               nfc_add_se(ndev->nfc_dev, ST_NCI_HCI_HOST_ID_ESE,
-                          NFC_SE_EMBEDDED);
+       if (info->se_info.se_status->is_ese_present) {
+               nfc_add_se(ndev->nfc_dev, ST_NCI_ESE_HOST_ID, NFC_SE_EMBEDDED);
                se_count++;
-               st_nci_se_get_atr(ndev);
        }
 
        return !se_count;
@@ -701,7 +730,7 @@ static void st_nci_se_activation_timeout(unsigned long data)
        complete(&info->se_info.req_completion);
 }
 
-int st_nci_se_init(struct nci_dev *ndev)
+int st_nci_se_init(struct nci_dev *ndev, struct st_nci_se_status *se_status)
 {
        struct st_nci_info *info = nci_get_drvdata(ndev);
 
@@ -723,6 +752,8 @@ int st_nci_se_init(struct nci_dev *ndev)
        info->se_info.wt_timeout =
                ST_NCI_BWI_TO_TIMEOUT(ST_NCI_ATR_DEFAULT_BWI);
 
+       info->se_info.se_status = se_status;
+
        return 0;
 }
 EXPORT_SYMBOL(st_nci_se_init);