linux/drivers/nfc/nxp-nci/firmware.c
<<
>>
Prefs
   1/*
   2 * Generic driver for NXP NCI NFC chips
   3 *
   4 * Copyright (C) 2014  NXP Semiconductors  All rights reserved.
   5 *
   6 * Author: Clément Perrochaud <clement.perrochaud@nxp.com>
   7 *
   8 * Derived from PN544 device driver:
   9 * Copyright (C) 2012  Intel Corporation. All rights reserved.
  10 *
  11 * This program is free software; you can redistribute it and/or modify it
  12 * under the terms and conditions of the GNU General Public License,
  13 * version 2, as published by the Free Software Foundation.
  14 *
  15 * This program is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 * GNU General Public License for more details.
  19 *
  20 * You should have received a copy of the GNU General Public License
  21 * along with this program; if not, see <http://www.gnu.org/licenses/>.
  22 */
  23
  24#include <linux/completion.h>
  25#include <linux/firmware.h>
  26#include <linux/nfc.h>
  27#include <linux/unaligned/access_ok.h>
  28
  29#include "nxp-nci.h"
  30
  31/* Crypto operations can take up to 30 seconds */
  32#define NXP_NCI_FW_ANSWER_TIMEOUT       msecs_to_jiffies(30000)
  33
  34#define NXP_NCI_FW_CMD_RESET            0xF0
  35#define NXP_NCI_FW_CMD_GETVERSION       0xF1
  36#define NXP_NCI_FW_CMD_CHECKINTEGRITY   0xE0
  37#define NXP_NCI_FW_CMD_WRITE            0xC0
  38#define NXP_NCI_FW_CMD_READ             0xA2
  39#define NXP_NCI_FW_CMD_GETSESSIONSTATE  0xF2
  40#define NXP_NCI_FW_CMD_LOG              0xA7
  41#define NXP_NCI_FW_CMD_FORCE            0xD0
  42#define NXP_NCI_FW_CMD_GET_DIE_ID       0xF4
  43
  44#define NXP_NCI_FW_CHUNK_FLAG   0x0400
  45
  46#define NXP_NCI_FW_RESULT_OK                            0x00
  47#define NXP_NCI_FW_RESULT_INVALID_ADDR                  0x01
  48#define NXP_NCI_FW_RESULT_GENERIC_ERROR                 0x02
  49#define NXP_NCI_FW_RESULT_UNKNOWN_CMD                   0x0B
  50#define NXP_NCI_FW_RESULT_ABORTED_CMD                   0x0C
  51#define NXP_NCI_FW_RESULT_PLL_ERROR                     0x0D
  52#define NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR          0x1E
  53#define NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR              0x1F
  54#define NXP_NCI_FW_RESULT_MEM_BSY                       0x20
  55#define NXP_NCI_FW_RESULT_SIGNATURE_ERROR               0x21
  56#define NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR        0x24
  57#define NXP_NCI_FW_RESULT_PROTOCOL_ERROR                0x28
  58#define NXP_NCI_FW_RESULT_SFWU_DEGRADED                 0x2A
  59#define NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK         0x2D
  60#define NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK          0x2E
  61#define NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5    0xC5
  62
  63void nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result)
  64{
  65        struct nxp_nci_fw_info *fw_info = &info->fw_info;
  66        int r;
  67
  68        if (info->phy_ops->set_mode) {
  69                r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
  70                if (r < 0 && result == 0)
  71                        result = -r;
  72        }
  73
  74        info->mode = NXP_NCI_MODE_COLD;
  75
  76        if (fw_info->fw) {
  77                release_firmware(fw_info->fw);
  78                fw_info->fw = NULL;
  79        }
  80
  81        nfc_fw_download_done(info->ndev->nfc_dev, fw_info->name, (u32) -result);
  82}
  83
  84/* crc_ccitt cannot be used since it is computed MSB first and not LSB first */
  85static u16 nxp_nci_fw_crc(u8 const *buffer, size_t len)
  86{
  87        u16 crc = 0xffff;
  88
  89        while (len--) {
  90                crc = ((crc >> 8) | (crc << 8)) ^ *buffer++;
  91                crc ^= (crc & 0xff) >> 4;
  92                crc ^= (crc & 0xff) << 12;
  93                crc ^= (crc & 0xff) << 5;
  94        }
  95
  96        return crc;
  97}
  98
  99static int nxp_nci_fw_send_chunk(struct nxp_nci_info *info)
 100{
 101        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 102        u16 header, crc;
 103        struct sk_buff *skb;
 104        size_t chunk_len;
 105        size_t remaining_len;
 106        int r;
 107
 108        skb = nci_skb_alloc(info->ndev, info->max_payload, GFP_KERNEL);
 109        if (!skb) {
 110                r = -ENOMEM;
 111                goto chunk_exit;
 112        }
 113
 114        chunk_len = info->max_payload - NXP_NCI_FW_HDR_LEN - NXP_NCI_FW_CRC_LEN;
 115        remaining_len = fw_info->frame_size - fw_info->written;
 116
 117        if (remaining_len > chunk_len) {
 118                header = NXP_NCI_FW_CHUNK_FLAG;
 119        } else {
 120                chunk_len = remaining_len;
 121                header = 0x0000;
 122        }
 123
 124        header |= chunk_len & NXP_NCI_FW_FRAME_LEN_MASK;
 125        put_unaligned_be16(header, skb_put(skb, NXP_NCI_FW_HDR_LEN));
 126
 127        memcpy(skb_put(skb, chunk_len), fw_info->data + fw_info->written,
 128               chunk_len);
 129
 130        crc = nxp_nci_fw_crc(skb->data, chunk_len + NXP_NCI_FW_HDR_LEN);
 131        put_unaligned_be16(crc, skb_put(skb, NXP_NCI_FW_CRC_LEN));
 132
 133        r = info->phy_ops->write(info->phy_id, skb);
 134        if (r >= 0)
 135                r = chunk_len;
 136
 137        kfree_skb(skb);
 138
 139chunk_exit:
 140        return r;
 141}
 142
 143static int nxp_nci_fw_send(struct nxp_nci_info *info)
 144{
 145        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 146        long completion_rc;
 147        int r;
 148
 149        reinit_completion(&fw_info->cmd_completion);
 150
 151        if (fw_info->written == 0) {
 152                fw_info->frame_size = get_unaligned_be16(fw_info->data) &
 153                                      NXP_NCI_FW_FRAME_LEN_MASK;
 154                fw_info->data += NXP_NCI_FW_HDR_LEN;
 155                fw_info->size -= NXP_NCI_FW_HDR_LEN;
 156        }
 157
 158        if (fw_info->frame_size > fw_info->size)
 159                return -EMSGSIZE;
 160
 161        r = nxp_nci_fw_send_chunk(info);
 162        if (r < 0)
 163                return r;
 164
 165        fw_info->written += r;
 166
 167        if (*fw_info->data == NXP_NCI_FW_CMD_RESET) {
 168                fw_info->cmd_result = 0;
 169                if (fw_info->fw)
 170                        schedule_work(&fw_info->work);
 171        } else {
 172                completion_rc = wait_for_completion_interruptible_timeout(
 173                        &fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT);
 174                if (completion_rc == 0)
 175                        return -ETIMEDOUT;
 176        }
 177
 178        return 0;
 179}
 180
 181void nxp_nci_fw_work(struct work_struct *work)
 182{
 183        struct nxp_nci_info *info;
 184        struct nxp_nci_fw_info *fw_info;
 185        int r;
 186
 187        fw_info = container_of(work, struct nxp_nci_fw_info, work);
 188        info = container_of(fw_info, struct nxp_nci_info, fw_info);
 189
 190        mutex_lock(&info->info_lock);
 191
 192        r = fw_info->cmd_result;
 193        if (r < 0)
 194                goto exit_work;
 195
 196        if (fw_info->written == fw_info->frame_size) {
 197                fw_info->data += fw_info->frame_size;
 198                fw_info->size -= fw_info->frame_size;
 199                fw_info->written = 0;
 200        }
 201
 202        if (fw_info->size > 0)
 203                r = nxp_nci_fw_send(info);
 204
 205exit_work:
 206        if (r < 0 || fw_info->size == 0)
 207                nxp_nci_fw_work_complete(info, r);
 208        mutex_unlock(&info->info_lock);
 209}
 210
 211int nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name)
 212{
 213        struct nxp_nci_info *info = nci_get_drvdata(ndev);
 214        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 215        int r;
 216
 217        mutex_lock(&info->info_lock);
 218
 219        if (!info->phy_ops->set_mode || !info->phy_ops->write) {
 220                r = -ENOTSUPP;
 221                goto fw_download_exit;
 222        }
 223
 224        if (!firmware_name || firmware_name[0] == '\0') {
 225                r = -EINVAL;
 226                goto fw_download_exit;
 227        }
 228
 229        strcpy(fw_info->name, firmware_name);
 230
 231        r = request_firmware(&fw_info->fw, firmware_name,
 232                             ndev->nfc_dev->dev.parent);
 233        if (r < 0)
 234                goto fw_download_exit;
 235
 236        r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW);
 237        if (r < 0) {
 238                release_firmware(fw_info->fw);
 239                goto fw_download_exit;
 240        }
 241
 242        info->mode = NXP_NCI_MODE_FW;
 243
 244        fw_info->data = fw_info->fw->data;
 245        fw_info->size = fw_info->fw->size;
 246        fw_info->written = 0;
 247        fw_info->frame_size = 0;
 248        fw_info->cmd_result = 0;
 249
 250        schedule_work(&fw_info->work);
 251
 252fw_download_exit:
 253        mutex_unlock(&info->info_lock);
 254        return r;
 255}
 256
 257static int nxp_nci_fw_read_status(u8 stat)
 258{
 259        switch (stat) {
 260        case NXP_NCI_FW_RESULT_OK:
 261                return 0;
 262        case NXP_NCI_FW_RESULT_INVALID_ADDR:
 263                return -EINVAL;
 264        case NXP_NCI_FW_RESULT_UNKNOWN_CMD:
 265                return -EINVAL;
 266        case NXP_NCI_FW_RESULT_ABORTED_CMD:
 267                return -EMSGSIZE;
 268        case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR:
 269                return -EADDRNOTAVAIL;
 270        case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR:
 271                return -ENOBUFS;
 272        case NXP_NCI_FW_RESULT_MEM_BSY:
 273                return -ENOKEY;
 274        case NXP_NCI_FW_RESULT_SIGNATURE_ERROR:
 275                return -EKEYREJECTED;
 276        case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR:
 277                return -EALREADY;
 278        case NXP_NCI_FW_RESULT_PROTOCOL_ERROR:
 279                return -EPROTO;
 280        case NXP_NCI_FW_RESULT_SFWU_DEGRADED:
 281                return -EHWPOISON;
 282        case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK:
 283                return 0;
 284        case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK:
 285                return 0;
 286        case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5:
 287                return -EINVAL;
 288        default:
 289                return -EIO;
 290        }
 291}
 292
 293static u16 nxp_nci_fw_check_crc(struct sk_buff *skb)
 294{
 295        u16 crc, frame_crc;
 296        size_t len = skb->len - NXP_NCI_FW_CRC_LEN;
 297
 298        crc = nxp_nci_fw_crc(skb->data, len);
 299        frame_crc = get_unaligned_be16(skb->data + len);
 300
 301        return (crc ^ frame_crc);
 302}
 303
 304void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
 305{
 306        struct nxp_nci_info *info = nci_get_drvdata(ndev);
 307        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 308
 309        complete(&fw_info->cmd_completion);
 310
 311        if (skb) {
 312                if (nxp_nci_fw_check_crc(skb) != 0x00)
 313                        fw_info->cmd_result = -EBADMSG;
 314                else
 315                        fw_info->cmd_result = nxp_nci_fw_read_status(
 316                                        *skb_pull(skb, NXP_NCI_FW_HDR_LEN));
 317                kfree_skb(skb);
 318        } else {
 319                fw_info->cmd_result = -EIO;
 320        }
 321
 322        if (fw_info->fw)
 323                schedule_work(&fw_info->work);
 324}
 325EXPORT_SYMBOL(nxp_nci_fw_recv_frame);
 326