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 <asm/unaligned.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        skb_put_data(skb, fw_info->data + fw_info->written, chunk_len);
 128
 129        crc = nxp_nci_fw_crc(skb->data, chunk_len + NXP_NCI_FW_HDR_LEN);
 130        put_unaligned_be16(crc, skb_put(skb, NXP_NCI_FW_CRC_LEN));
 131
 132        r = info->phy_ops->write(info->phy_id, skb);
 133        if (r >= 0)
 134                r = chunk_len;
 135
 136        kfree_skb(skb);
 137
 138chunk_exit:
 139        return r;
 140}
 141
 142static int nxp_nci_fw_send(struct nxp_nci_info *info)
 143{
 144        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 145        long completion_rc;
 146        int r;
 147
 148        reinit_completion(&fw_info->cmd_completion);
 149
 150        if (fw_info->written == 0) {
 151                fw_info->frame_size = get_unaligned_be16(fw_info->data) &
 152                                      NXP_NCI_FW_FRAME_LEN_MASK;
 153                fw_info->data += NXP_NCI_FW_HDR_LEN;
 154                fw_info->size -= NXP_NCI_FW_HDR_LEN;
 155        }
 156
 157        if (fw_info->frame_size > fw_info->size)
 158                return -EMSGSIZE;
 159
 160        r = nxp_nci_fw_send_chunk(info);
 161        if (r < 0)
 162                return r;
 163
 164        fw_info->written += r;
 165
 166        if (*fw_info->data == NXP_NCI_FW_CMD_RESET) {
 167                fw_info->cmd_result = 0;
 168                if (fw_info->fw)
 169                        schedule_work(&fw_info->work);
 170        } else {
 171                completion_rc = wait_for_completion_interruptible_timeout(
 172                        &fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT);
 173                if (completion_rc == 0)
 174                        return -ETIMEDOUT;
 175        }
 176
 177        return 0;
 178}
 179
 180void nxp_nci_fw_work(struct work_struct *work)
 181{
 182        struct nxp_nci_info *info;
 183        struct nxp_nci_fw_info *fw_info;
 184        int r;
 185
 186        fw_info = container_of(work, struct nxp_nci_fw_info, work);
 187        info = container_of(fw_info, struct nxp_nci_info, fw_info);
 188
 189        mutex_lock(&info->info_lock);
 190
 191        r = fw_info->cmd_result;
 192        if (r < 0)
 193                goto exit_work;
 194
 195        if (fw_info->written == fw_info->frame_size) {
 196                fw_info->data += fw_info->frame_size;
 197                fw_info->size -= fw_info->frame_size;
 198                fw_info->written = 0;
 199        }
 200
 201        if (fw_info->size > 0)
 202                r = nxp_nci_fw_send(info);
 203
 204exit_work:
 205        if (r < 0 || fw_info->size == 0)
 206                nxp_nci_fw_work_complete(info, r);
 207        mutex_unlock(&info->info_lock);
 208}
 209
 210int nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name)
 211{
 212        struct nxp_nci_info *info = nci_get_drvdata(ndev);
 213        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 214        int r;
 215
 216        mutex_lock(&info->info_lock);
 217
 218        if (!info->phy_ops->set_mode || !info->phy_ops->write) {
 219                r = -ENOTSUPP;
 220                goto fw_download_exit;
 221        }
 222
 223        if (!firmware_name || firmware_name[0] == '\0') {
 224                r = -EINVAL;
 225                goto fw_download_exit;
 226        }
 227
 228        strcpy(fw_info->name, firmware_name);
 229
 230        r = request_firmware(&fw_info->fw, firmware_name,
 231                             ndev->nfc_dev->dev.parent);
 232        if (r < 0)
 233                goto fw_download_exit;
 234
 235        r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW);
 236        if (r < 0) {
 237                release_firmware(fw_info->fw);
 238                goto fw_download_exit;
 239        }
 240
 241        info->mode = NXP_NCI_MODE_FW;
 242
 243        fw_info->data = fw_info->fw->data;
 244        fw_info->size = fw_info->fw->size;
 245        fw_info->written = 0;
 246        fw_info->frame_size = 0;
 247        fw_info->cmd_result = 0;
 248
 249        schedule_work(&fw_info->work);
 250
 251fw_download_exit:
 252        mutex_unlock(&info->info_lock);
 253        return r;
 254}
 255
 256static int nxp_nci_fw_read_status(u8 stat)
 257{
 258        switch (stat) {
 259        case NXP_NCI_FW_RESULT_OK:
 260                return 0;
 261        case NXP_NCI_FW_RESULT_INVALID_ADDR:
 262                return -EINVAL;
 263        case NXP_NCI_FW_RESULT_UNKNOWN_CMD:
 264                return -EINVAL;
 265        case NXP_NCI_FW_RESULT_ABORTED_CMD:
 266                return -EMSGSIZE;
 267        case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR:
 268                return -EADDRNOTAVAIL;
 269        case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR:
 270                return -ENOBUFS;
 271        case NXP_NCI_FW_RESULT_MEM_BSY:
 272                return -ENOKEY;
 273        case NXP_NCI_FW_RESULT_SIGNATURE_ERROR:
 274                return -EKEYREJECTED;
 275        case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR:
 276                return -EALREADY;
 277        case NXP_NCI_FW_RESULT_PROTOCOL_ERROR:
 278                return -EPROTO;
 279        case NXP_NCI_FW_RESULT_SFWU_DEGRADED:
 280                return -EHWPOISON;
 281        case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK:
 282                return 0;
 283        case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK:
 284                return 0;
 285        case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5:
 286                return -EINVAL;
 287        default:
 288                return -EIO;
 289        }
 290}
 291
 292static u16 nxp_nci_fw_check_crc(struct sk_buff *skb)
 293{
 294        u16 crc, frame_crc;
 295        size_t len = skb->len - NXP_NCI_FW_CRC_LEN;
 296
 297        crc = nxp_nci_fw_crc(skb->data, len);
 298        frame_crc = get_unaligned_be16(skb->data + len);
 299
 300        return (crc ^ frame_crc);
 301}
 302
 303void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
 304{
 305        struct nxp_nci_info *info = nci_get_drvdata(ndev);
 306        struct nxp_nci_fw_info *fw_info = &info->fw_info;
 307
 308        complete(&fw_info->cmd_completion);
 309
 310        if (skb) {
 311                if (nxp_nci_fw_check_crc(skb) != 0x00)
 312                        fw_info->cmd_result = -EBADMSG;
 313                else
 314                        fw_info->cmd_result = nxp_nci_fw_read_status(*(u8 *)skb_pull(skb, NXP_NCI_FW_HDR_LEN));
 315                kfree_skb(skb);
 316        } else {
 317                fw_info->cmd_result = -EIO;
 318        }
 319
 320        if (fw_info->fw)
 321                schedule_work(&fw_info->work);
 322}
 323EXPORT_SYMBOL(nxp_nci_fw_recv_frame);
 324