2019-05-31 01:09:57 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2015-03-09 11:12:04 +01:00
/*
* Generic driver for NXP NCI NFC chips
*
* Copyright ( C ) 2014 NXP Semiconductors All rights reserved .
*
* Author : Clément Perrochaud < clement . perrochaud @ nxp . com >
*
* Derived from PN544 device driver :
* Copyright ( C ) 2012 Intel Corporation . All rights reserved .
*/
# include <linux/completion.h>
# include <linux/firmware.h>
# include <linux/nfc.h>
2015-08-01 06:59:29 -07:00
# include <asm/unaligned.h>
2015-03-09 11:12:04 +01:00
# include "nxp-nci.h"
/* Crypto operations can take up to 30 seconds */
# define NXP_NCI_FW_ANSWER_TIMEOUT msecs_to_jiffies(30000)
# define NXP_NCI_FW_CMD_RESET 0xF0
# define NXP_NCI_FW_CMD_GETVERSION 0xF1
# define NXP_NCI_FW_CMD_CHECKINTEGRITY 0xE0
# define NXP_NCI_FW_CMD_WRITE 0xC0
# define NXP_NCI_FW_CMD_READ 0xA2
# define NXP_NCI_FW_CMD_GETSESSIONSTATE 0xF2
# define NXP_NCI_FW_CMD_LOG 0xA7
# define NXP_NCI_FW_CMD_FORCE 0xD0
# define NXP_NCI_FW_CMD_GET_DIE_ID 0xF4
# define NXP_NCI_FW_CHUNK_FLAG 0x0400
# define NXP_NCI_FW_RESULT_OK 0x00
# define NXP_NCI_FW_RESULT_INVALID_ADDR 0x01
# define NXP_NCI_FW_RESULT_GENERIC_ERROR 0x02
# define NXP_NCI_FW_RESULT_UNKNOWN_CMD 0x0B
# define NXP_NCI_FW_RESULT_ABORTED_CMD 0x0C
# define NXP_NCI_FW_RESULT_PLL_ERROR 0x0D
# define NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR 0x1E
# define NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR 0x1F
# define NXP_NCI_FW_RESULT_MEM_BSY 0x20
# define NXP_NCI_FW_RESULT_SIGNATURE_ERROR 0x21
# define NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR 0x24
# define NXP_NCI_FW_RESULT_PROTOCOL_ERROR 0x28
# define NXP_NCI_FW_RESULT_SFWU_DEGRADED 0x2A
# define NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK 0x2D
# define NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK 0x2E
# define NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5 0xC5
void nxp_nci_fw_work_complete ( struct nxp_nci_info * info , int result )
{
struct nxp_nci_fw_info * fw_info = & info - > fw_info ;
int r ;
if ( info - > phy_ops - > set_mode ) {
r = info - > phy_ops - > set_mode ( info - > phy_id , NXP_NCI_MODE_COLD ) ;
if ( r < 0 & & result = = 0 )
result = - r ;
}
info - > mode = NXP_NCI_MODE_COLD ;
if ( fw_info - > fw ) {
release_firmware ( fw_info - > fw ) ;
fw_info - > fw = NULL ;
}
nfc_fw_download_done ( info - > ndev - > nfc_dev , fw_info - > name , ( u32 ) - result ) ;
}
/* crc_ccitt cannot be used since it is computed MSB first and not LSB first */
static u16 nxp_nci_fw_crc ( u8 const * buffer , size_t len )
{
u16 crc = 0xffff ;
while ( len - - ) {
crc = ( ( crc > > 8 ) | ( crc < < 8 ) ) ^ * buffer + + ;
crc ^ = ( crc & 0xff ) > > 4 ;
crc ^ = ( crc & 0xff ) < < 12 ;
crc ^ = ( crc & 0xff ) < < 5 ;
}
return crc ;
}
static int nxp_nci_fw_send_chunk ( struct nxp_nci_info * info )
{
struct nxp_nci_fw_info * fw_info = & info - > fw_info ;
u16 header , crc ;
struct sk_buff * skb ;
size_t chunk_len ;
size_t remaining_len ;
int r ;
skb = nci_skb_alloc ( info - > ndev , info - > max_payload , GFP_KERNEL ) ;
2021-06-18 16:52:26 +08:00
if ( ! skb )
return - ENOMEM ;
2015-03-09 11:12:04 +01:00
chunk_len = info - > max_payload - NXP_NCI_FW_HDR_LEN - NXP_NCI_FW_CRC_LEN ;
remaining_len = fw_info - > frame_size - fw_info - > written ;
if ( remaining_len > chunk_len ) {
header = NXP_NCI_FW_CHUNK_FLAG ;
} else {
chunk_len = remaining_len ;
header = 0x0000 ;
}
header | = chunk_len & NXP_NCI_FW_FRAME_LEN_MASK ;
put_unaligned_be16 ( header , skb_put ( skb , NXP_NCI_FW_HDR_LEN ) ) ;
networking: introduce and use skb_put_data()
A common pattern with skb_put() is to just want to memcpy()
some data into the new space, introduce skb_put_data() for
this.
An spatch similar to the one for skb_put_zero() converts many
of the places using it:
@@
identifier p, p2;
expression len, skb, data;
type t, t2;
@@
(
-p = skb_put(skb, len);
+p = skb_put_data(skb, data, len);
|
-p = (t)skb_put(skb, len);
+p = skb_put_data(skb, data, len);
)
(
p2 = (t2)p;
-memcpy(p2, data, len);
|
-memcpy(p, data, len);
)
@@
type t, t2;
identifier p, p2;
expression skb, data;
@@
t *p;
...
(
-p = skb_put(skb, sizeof(t));
+p = skb_put_data(skb, data, sizeof(t));
|
-p = (t *)skb_put(skb, sizeof(t));
+p = skb_put_data(skb, data, sizeof(t));
)
(
p2 = (t2)p;
-memcpy(p2, data, sizeof(*p));
|
-memcpy(p, data, sizeof(*p));
)
@@
expression skb, len, data;
@@
-memcpy(skb_put(skb, len), data, len);
+skb_put_data(skb, data, len);
(again, manually post-processed to retain some comments)
Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:20 +02:00
skb_put_data ( skb , fw_info - > data + fw_info - > written , chunk_len ) ;
2015-03-09 11:12:04 +01:00
crc = nxp_nci_fw_crc ( skb - > data , chunk_len + NXP_NCI_FW_HDR_LEN ) ;
put_unaligned_be16 ( crc , skb_put ( skb , NXP_NCI_FW_CRC_LEN ) ) ;
r = info - > phy_ops - > write ( info - > phy_id , skb ) ;
if ( r > = 0 )
r = chunk_len ;
kfree_skb ( skb ) ;
return r ;
}
static int nxp_nci_fw_send ( struct nxp_nci_info * info )
{
struct nxp_nci_fw_info * fw_info = & info - > fw_info ;
long completion_rc ;
int r ;
reinit_completion ( & fw_info - > cmd_completion ) ;
if ( fw_info - > written = = 0 ) {
fw_info - > frame_size = get_unaligned_be16 ( fw_info - > data ) &
NXP_NCI_FW_FRAME_LEN_MASK ;
fw_info - > data + = NXP_NCI_FW_HDR_LEN ;
fw_info - > size - = NXP_NCI_FW_HDR_LEN ;
}
if ( fw_info - > frame_size > fw_info - > size )
return - EMSGSIZE ;
r = nxp_nci_fw_send_chunk ( info ) ;
if ( r < 0 )
return r ;
fw_info - > written + = r ;
if ( * fw_info - > data = = NXP_NCI_FW_CMD_RESET ) {
fw_info - > cmd_result = 0 ;
if ( fw_info - > fw )
schedule_work ( & fw_info - > work ) ;
} else {
completion_rc = wait_for_completion_interruptible_timeout (
& fw_info - > cmd_completion , NXP_NCI_FW_ANSWER_TIMEOUT ) ;
if ( completion_rc = = 0 )
return - ETIMEDOUT ;
}
return 0 ;
}
void nxp_nci_fw_work ( struct work_struct * work )
{
struct nxp_nci_info * info ;
struct nxp_nci_fw_info * fw_info ;
int r ;
fw_info = container_of ( work , struct nxp_nci_fw_info , work ) ;
info = container_of ( fw_info , struct nxp_nci_info , fw_info ) ;
mutex_lock ( & info - > info_lock ) ;
r = fw_info - > cmd_result ;
if ( r < 0 )
goto exit_work ;
if ( fw_info - > written = = fw_info - > frame_size ) {
fw_info - > data + = fw_info - > frame_size ;
fw_info - > size - = fw_info - > frame_size ;
fw_info - > written = 0 ;
}
if ( fw_info - > size > 0 )
r = nxp_nci_fw_send ( info ) ;
exit_work :
if ( r < 0 | | fw_info - > size = = 0 )
nxp_nci_fw_work_complete ( info , r ) ;
mutex_unlock ( & info - > info_lock ) ;
}
int nxp_nci_fw_download ( struct nci_dev * ndev , const char * firmware_name )
{
struct nxp_nci_info * info = nci_get_drvdata ( ndev ) ;
struct nxp_nci_fw_info * fw_info = & info - > fw_info ;
int r ;
mutex_lock ( & info - > info_lock ) ;
if ( ! info - > phy_ops - > set_mode | | ! info - > phy_ops - > write ) {
r = - ENOTSUPP ;
goto fw_download_exit ;
}
if ( ! firmware_name | | firmware_name [ 0 ] = = ' \0 ' ) {
r = - EINVAL ;
goto fw_download_exit ;
}
strcpy ( fw_info - > name , firmware_name ) ;
r = request_firmware ( & fw_info - > fw , firmware_name ,
ndev - > nfc_dev - > dev . parent ) ;
if ( r < 0 )
goto fw_download_exit ;
r = info - > phy_ops - > set_mode ( info - > phy_id , NXP_NCI_MODE_FW ) ;
2015-04-06 00:58:51 +02:00
if ( r < 0 ) {
release_firmware ( fw_info - > fw ) ;
2015-03-09 11:12:04 +01:00
goto fw_download_exit ;
2015-04-06 00:58:51 +02:00
}
2015-03-09 11:12:04 +01:00
info - > mode = NXP_NCI_MODE_FW ;
fw_info - > data = fw_info - > fw - > data ;
fw_info - > size = fw_info - > fw - > size ;
fw_info - > written = 0 ;
fw_info - > frame_size = 0 ;
fw_info - > cmd_result = 0 ;
2015-04-06 00:51:17 +02:00
schedule_work ( & fw_info - > work ) ;
2015-03-09 11:12:04 +01:00
fw_download_exit :
mutex_unlock ( & info - > info_lock ) ;
return r ;
}
static int nxp_nci_fw_read_status ( u8 stat )
{
switch ( stat ) {
case NXP_NCI_FW_RESULT_OK :
return 0 ;
case NXP_NCI_FW_RESULT_INVALID_ADDR :
return - EINVAL ;
case NXP_NCI_FW_RESULT_UNKNOWN_CMD :
return - EINVAL ;
case NXP_NCI_FW_RESULT_ABORTED_CMD :
return - EMSGSIZE ;
case NXP_NCI_FW_RESULT_ADDR_RANGE_OFL_ERROR :
return - EADDRNOTAVAIL ;
case NXP_NCI_FW_RESULT_BUFFER_OFL_ERROR :
return - ENOBUFS ;
case NXP_NCI_FW_RESULT_MEM_BSY :
return - ENOKEY ;
case NXP_NCI_FW_RESULT_SIGNATURE_ERROR :
return - EKEYREJECTED ;
case NXP_NCI_FW_RESULT_FIRMWARE_VERSION_ERROR :
return - EALREADY ;
case NXP_NCI_FW_RESULT_PROTOCOL_ERROR :
return - EPROTO ;
case NXP_NCI_FW_RESULT_SFWU_DEGRADED :
return - EHWPOISON ;
case NXP_NCI_FW_RESULT_PH_STATUS_FIRST_CHUNK :
return 0 ;
case NXP_NCI_FW_RESULT_PH_STATUS_NEXT_CHUNK :
return 0 ;
case NXP_NCI_FW_RESULT_PH_STATUS_INTERNAL_ERROR_5 :
return - EINVAL ;
default :
return - EIO ;
}
}
static u16 nxp_nci_fw_check_crc ( struct sk_buff * skb )
{
u16 crc , frame_crc ;
size_t len = skb - > len - NXP_NCI_FW_CRC_LEN ;
crc = nxp_nci_fw_crc ( skb - > data , len ) ;
frame_crc = get_unaligned_be16 ( skb - > data + len ) ;
return ( crc ^ frame_crc ) ;
}
void nxp_nci_fw_recv_frame ( struct nci_dev * ndev , struct sk_buff * skb )
{
struct nxp_nci_info * info = nci_get_drvdata ( ndev ) ;
struct nxp_nci_fw_info * fw_info = & info - > fw_info ;
complete ( & fw_info - > cmd_completion ) ;
if ( skb ) {
if ( nxp_nci_fw_check_crc ( skb ) ! = 0x00 )
fw_info - > cmd_result = - EBADMSG ;
else
networking: make skb_pull & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions return void * and remove all the casts across
the tree, adding a (u8 *) cast only where the unsigned char pointer
was used directly, all done with the following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = {
skb_pull,
__skb_pull,
skb_pull_inline,
__pskb_pull_tail,
__pskb_pull,
pskb_pull
};
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = {
skb_pull,
__skb_pull,
skb_pull_inline,
__pskb_pull_tail,
__pskb_pull,
pskb_pull
};
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:22 +02:00
fw_info - > cmd_result = nxp_nci_fw_read_status ( * ( u8 * ) skb_pull ( skb , NXP_NCI_FW_HDR_LEN ) ) ;
2015-03-09 11:12:04 +01:00
kfree_skb ( skb ) ;
} else {
fw_info - > cmd_result = - EIO ;
}
if ( fw_info - > fw )
schedule_work ( & fw_info - > work ) ;
}
EXPORT_SYMBOL ( nxp_nci_fw_recv_frame ) ;