2012-02-07 16:18:40 +04:00
/******************************************************************************
*
* This file is provided under a dual BSD / GPLv2 license . When using or
* redistributing this file , you may do so under either license .
*
* GPL LICENSE SUMMARY
*
* Copyright ( c ) 2007 - 2012 Intel Corporation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 ,
* USA
*
* The full GNU General Public License is included in this distribution
* in the file called LICENSE . GPL .
*
* Contact Information :
* Intel Linux Wireless < ilw @ linux . intel . com >
* Intel Corporation , 5200 N . E . Elam Young Parkway , Hillsboro , OR 97124 - 6497
*
* BSD LICENSE
*
* Copyright ( c ) 2005 - 2012 Intel Corporation . All rights reserved .
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
*
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in
* the documentation and / or other materials provided with the
* distribution .
* * Neither the name Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
* LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <linux/completion.h>
2012-03-05 23:24:50 +04:00
# include <linux/dma-mapping.h>
# include <linux/firmware.h>
# include <linux/module.h>
2012-02-07 16:18:40 +04:00
# include "iwl-drv.h"
# include "iwl-trans.h"
2012-03-05 23:24:50 +04:00
# include "iwl-shared.h"
2012-02-09 18:08:15 +04:00
# include "iwl-op-mode.h"
2012-03-11 01:00:12 +04:00
# include "iwl-agn-hw.h"
2012-02-07 16:18:40 +04:00
2012-03-07 01:30:37 +04:00
/* private includes */
2012-03-07 01:30:44 +04:00
# include "iwl-fw-file.h"
2012-03-07 01:30:37 +04:00
2012-03-07 01:30:38 +04:00
/**
* struct iwl_drv - drv common data
* @ fw : the iwl_fw structure
* @ shrd : pointer to common shared structure
* @ op_mode : the running op_mode
2012-03-22 19:51:44 +04:00
* @ trans : transport layer
* @ cfg : configuration struct
2012-03-07 01:30:38 +04:00
* @ fw_index : firmware revision to try loading
* @ firmware_name : composite filename of ucode file to load
* @ request_firmware_complete : the firmware has been obtained from user space
*/
struct iwl_drv {
struct iwl_fw fw ;
struct iwl_shared * shrd ;
struct iwl_op_mode * op_mode ;
2012-03-26 19:51:09 +04:00
struct iwl_trans * trans ;
2012-03-22 19:51:44 +04:00
const struct iwl_cfg * cfg ;
2012-03-07 01:30:38 +04:00
int fw_index ; /* firmware we're trying to load */
char firmware_name [ 25 ] ; /* name of firmware file to load */
struct completion request_firmware_complete ;
} ;
2012-03-11 01:00:12 +04:00
/*
* struct fw_sec : Just for the image parsing proccess .
* For the fw storage we are using struct fw_desc .
*/
struct fw_sec {
const void * data ; /* the sec data */
size_t size ; /* section size */
u32 offset ; /* offset of writing in the device */
} ;
2012-03-07 01:30:38 +04:00
static void iwl_free_fw_desc ( struct iwl_drv * drv , struct fw_desc * desc )
2012-03-05 23:24:50 +04:00
{
if ( desc - > v_addr )
2012-03-22 19:51:44 +04:00
dma_free_coherent ( drv - > trans - > dev , desc - > len ,
2012-03-05 23:24:50 +04:00
desc - > v_addr , desc - > p_addr ) ;
desc - > v_addr = NULL ;
desc - > len = 0 ;
}
2012-03-07 01:30:38 +04:00
static void iwl_free_fw_img ( struct iwl_drv * drv , struct fw_img * img )
2012-03-05 23:24:50 +04:00
{
2012-03-11 01:00:14 +04:00
int i ;
for ( i = 0 ; i < IWL_UCODE_SECTION_MAX ; i + + )
iwl_free_fw_desc ( drv , & img - > sec [ i ] ) ;
2012-03-05 23:24:50 +04:00
}
2012-03-07 01:30:38 +04:00
static void iwl_dealloc_ucode ( struct iwl_drv * drv )
2012-03-05 23:24:50 +04:00
{
2012-03-11 01:00:14 +04:00
int i ;
for ( i = 0 ; i < IWL_UCODE_TYPE_MAX ; i + + )
iwl_free_fw_img ( drv , drv - > fw . img + i ) ;
2012-03-05 23:24:50 +04:00
}
2012-03-07 01:30:38 +04:00
static int iwl_alloc_fw_desc ( struct iwl_drv * drv , struct fw_desc * desc ,
2012-03-11 01:00:12 +04:00
struct fw_sec * sec )
2012-03-05 23:24:50 +04:00
{
2012-03-11 01:00:12 +04:00
if ( ! sec | | ! sec - > size ) {
2012-03-05 23:24:50 +04:00
desc - > v_addr = NULL ;
return - EINVAL ;
}
2012-03-22 19:51:44 +04:00
desc - > v_addr = dma_alloc_coherent ( drv - > trans - > dev , sec - > size ,
2012-03-05 23:24:50 +04:00
& desc - > p_addr , GFP_KERNEL ) ;
if ( ! desc - > v_addr )
return - ENOMEM ;
2012-03-11 01:00:12 +04:00
desc - > len = sec - > size ;
desc - > offset = sec - > offset ;
memcpy ( desc - > v_addr , sec - > data , sec - > size ) ;
2012-03-05 23:24:50 +04:00
return 0 ;
}
static void iwl_ucode_callback ( const struct firmware * ucode_raw , void * context ) ;
# define UCODE_EXPERIMENTAL_INDEX 100
# define UCODE_EXPERIMENTAL_TAG "exp"
2012-03-07 01:30:38 +04:00
static int iwl_request_firmware ( struct iwl_drv * drv , bool first )
2012-03-05 23:24:50 +04:00
{
2012-03-22 19:51:44 +04:00
const char * name_pre = drv - > cfg - > fw_name_pre ;
2012-03-05 23:24:50 +04:00
char tag [ 8 ] ;
if ( first ) {
# ifdef CONFIG_IWLWIFI_DEBUG_EXPERIMENTAL_UCODE
2012-03-07 01:30:38 +04:00
drv - > fw_index = UCODE_EXPERIMENTAL_INDEX ;
2012-03-05 23:24:50 +04:00
strcpy ( tag , UCODE_EXPERIMENTAL_TAG ) ;
2012-03-07 01:30:38 +04:00
} else if ( drv - > fw_index = = UCODE_EXPERIMENTAL_INDEX ) {
2012-03-05 23:24:50 +04:00
# endif
2012-03-22 19:51:44 +04:00
drv - > fw_index = drv - > cfg - > ucode_api_max ;
2012-03-07 01:30:38 +04:00
sprintf ( tag , " %d " , drv - > fw_index ) ;
2012-03-05 23:24:50 +04:00
} else {
2012-03-07 01:30:38 +04:00
drv - > fw_index - - ;
sprintf ( tag , " %d " , drv - > fw_index ) ;
2012-03-05 23:24:50 +04:00
}
2012-03-22 19:51:44 +04:00
if ( drv - > fw_index < drv - > cfg - > ucode_api_min ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " no suitable firmware found! \n " ) ;
2012-03-05 23:24:50 +04:00
return - ENOENT ;
}
2012-03-07 01:30:38 +04:00
sprintf ( drv - > firmware_name , " %s%s%s " , name_pre , tag , " .ucode " ) ;
2012-03-05 23:24:50 +04:00
2012-03-07 01:30:38 +04:00
IWL_DEBUG_INFO ( drv , " attempting to load firmware %s'%s' \n " ,
( drv - > fw_index = = UCODE_EXPERIMENTAL_INDEX )
2012-03-05 23:24:50 +04:00
? " EXPERIMENTAL " : " " ,
2012-03-07 01:30:38 +04:00
drv - > firmware_name ) ;
2012-03-05 23:24:50 +04:00
2012-03-07 01:30:38 +04:00
return request_firmware_nowait ( THIS_MODULE , 1 , drv - > firmware_name ,
2012-03-22 19:51:44 +04:00
drv - > trans - > dev ,
2012-03-07 01:30:38 +04:00
GFP_KERNEL , drv , iwl_ucode_callback ) ;
2012-03-05 23:24:50 +04:00
}
2012-03-11 01:00:12 +04:00
struct fw_img_parsing {
2012-03-11 01:00:14 +04:00
struct fw_sec sec [ IWL_UCODE_SECTION_MAX ] ;
2012-03-11 01:00:12 +04:00
int sec_counter ;
} ;
2012-03-11 01:00:13 +04:00
/*
* struct fw_sec_parsing : to extract fw section and it ' s offset from tlv
*/
struct fw_sec_parsing {
__le32 offset ;
const u8 data [ ] ;
} __packed ;
/**
* struct iwl_tlv_calib_data - parse the default calib data from TLV
*
* @ ucode_type : the uCode to which the following default calib relates .
* @ calib : default calibrations .
*/
struct iwl_tlv_calib_data {
__le32 ucode_type ;
__le64 calib ;
} __packed ;
2012-03-11 01:00:12 +04:00
struct iwl_firmware_pieces {
struct fw_img_parsing img [ IWL_UCODE_TYPE_MAX ] ;
2012-03-05 23:24:50 +04:00
u32 init_evtlog_ptr , init_evtlog_size , init_errlog_ptr ;
u32 inst_evtlog_ptr , inst_evtlog_size , inst_errlog_ptr ;
} ;
2012-03-11 01:00:12 +04:00
/*
* These functions are just to extract uCode section data from the pieces
* structure .
*/
static struct fw_sec * get_sec ( struct iwl_firmware_pieces * pieces ,
enum iwl_ucode_type type ,
int sec )
{
return & pieces - > img [ type ] . sec [ sec ] ;
}
static void set_sec_data ( struct iwl_firmware_pieces * pieces ,
enum iwl_ucode_type type ,
int sec ,
const void * data )
{
pieces - > img [ type ] . sec [ sec ] . data = data ;
}
static void set_sec_size ( struct iwl_firmware_pieces * pieces ,
enum iwl_ucode_type type ,
int sec ,
size_t size )
{
pieces - > img [ type ] . sec [ sec ] . size = size ;
}
static size_t get_sec_size ( struct iwl_firmware_pieces * pieces ,
enum iwl_ucode_type type ,
int sec )
{
return pieces - > img [ type ] . sec [ sec ] . size ;
}
static void set_sec_offset ( struct iwl_firmware_pieces * pieces ,
enum iwl_ucode_type type ,
int sec ,
u32 offset )
{
pieces - > img [ type ] . sec [ sec ] . offset = offset ;
}
2012-03-11 01:00:13 +04:00
/*
* Gets uCode section from tlv .
*/
static int iwl_store_ucode_sec ( struct iwl_firmware_pieces * pieces ,
const void * data , enum iwl_ucode_type type ,
int size )
{
struct fw_img_parsing * img ;
struct fw_sec * sec ;
struct fw_sec_parsing * sec_parse ;
if ( WARN_ON ( ! pieces | | ! data | | type > = IWL_UCODE_TYPE_MAX ) )
return - 1 ;
sec_parse = ( struct fw_sec_parsing * ) data ;
img = & pieces - > img [ type ] ;
sec = & img - > sec [ img - > sec_counter ] ;
sec - > offset = le32_to_cpu ( sec_parse - > offset ) ;
sec - > data = sec_parse - > data ;
2012-03-13 16:32:48 +04:00
sec - > size = size - sizeof ( sec_parse - > offset ) ;
2012-03-11 01:00:13 +04:00
+ + img - > sec_counter ;
return 0 ;
}
static int iwl_set_default_calib ( struct iwl_drv * drv , const u8 * data )
{
struct iwl_tlv_calib_data * def_calib =
( struct iwl_tlv_calib_data * ) data ;
u32 ucode_type = le32_to_cpu ( def_calib - > ucode_type ) ;
if ( ucode_type > = IWL_UCODE_TYPE_MAX ) {
IWL_ERR ( drv , " Wrong ucode_type %u for default calibration. \n " ,
ucode_type ) ;
return - EINVAL ;
}
drv - > fw . default_calib [ ucode_type ] = le64_to_cpu ( def_calib - > calib ) ;
return 0 ;
}
2012-03-07 01:30:38 +04:00
static int iwl_parse_v1_v2_firmware ( struct iwl_drv * drv ,
2012-03-11 01:00:12 +04:00
const struct firmware * ucode_raw ,
struct iwl_firmware_pieces * pieces )
2012-03-05 23:24:50 +04:00
{
struct iwl_ucode_header * ucode = ( void * ) ucode_raw - > data ;
u32 api_ver , hdr_size , build ;
char buildstr [ 25 ] ;
const u8 * src ;
2012-03-07 01:30:38 +04:00
drv - > fw . ucode_ver = le32_to_cpu ( ucode - > ver ) ;
api_ver = IWL_UCODE_API ( drv - > fw . ucode_ver ) ;
2012-03-05 23:24:50 +04:00
switch ( api_ver ) {
default :
hdr_size = 28 ;
if ( ucode_raw - > size < hdr_size ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " File size too small! \n " ) ;
2012-03-05 23:24:50 +04:00
return - EINVAL ;
}
build = le32_to_cpu ( ucode - > u . v2 . build ) ;
2012-03-11 01:00:12 +04:00
set_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST ,
le32_to_cpu ( ucode - > u . v2 . inst_size ) ) ;
set_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA ,
le32_to_cpu ( ucode - > u . v2 . data_size ) ) ;
set_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ,
le32_to_cpu ( ucode - > u . v2 . init_size ) ) ;
set_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ,
le32_to_cpu ( ucode - > u . v2 . init_data_size ) ) ;
2012-03-05 23:24:50 +04:00
src = ucode - > u . v2 . data ;
break ;
case 0 :
case 1 :
case 2 :
hdr_size = 24 ;
if ( ucode_raw - > size < hdr_size ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " File size too small! \n " ) ;
2012-03-05 23:24:50 +04:00
return - EINVAL ;
}
build = 0 ;
2012-03-11 01:00:12 +04:00
set_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST ,
le32_to_cpu ( ucode - > u . v1 . inst_size ) ) ;
set_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA ,
le32_to_cpu ( ucode - > u . v1 . data_size ) ) ;
set_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ,
le32_to_cpu ( ucode - > u . v1 . init_size ) ) ;
set_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ,
le32_to_cpu ( ucode - > u . v1 . init_data_size ) ) ;
2012-03-05 23:24:50 +04:00
src = ucode - > u . v1 . data ;
break ;
}
if ( build )
sprintf ( buildstr , " build %u%s " , build ,
2012-03-07 01:30:38 +04:00
( drv - > fw_index = = UCODE_EXPERIMENTAL_INDEX )
2012-03-05 23:24:50 +04:00
? " (EXP) " : " " ) ;
else
buildstr [ 0 ] = ' \0 ' ;
2012-03-07 01:30:38 +04:00
snprintf ( drv - > fw . fw_version ,
sizeof ( drv - > fw . fw_version ) ,
2012-03-05 23:24:50 +04:00
" %u.%u.%u.%u%s " ,
2012-03-07 01:30:38 +04:00
IWL_UCODE_MAJOR ( drv - > fw . ucode_ver ) ,
IWL_UCODE_MINOR ( drv - > fw . ucode_ver ) ,
IWL_UCODE_API ( drv - > fw . ucode_ver ) ,
IWL_UCODE_SERIAL ( drv - > fw . ucode_ver ) ,
2012-03-05 23:24:50 +04:00
buildstr ) ;
/* Verify size of file vs. image size info in file's header */
2012-03-11 01:00:12 +04:00
if ( ucode_raw - > size ! = hdr_size +
get_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST ) +
get_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA ) +
get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ) +
get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ) ) {
2012-03-05 23:24:50 +04:00
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv ,
2012-03-05 23:24:50 +04:00
" uCode file size %d does not match expected size \n " ,
( int ) ucode_raw - > size ) ;
return - EINVAL ;
}
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST , src ) ;
src + = get_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST ) ;
set_sec_offset ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST ,
IWLAGN_RTC_INST_LOWER_BOUND ) ;
set_sec_data ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA , src ) ;
src + = get_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA ) ;
set_sec_offset ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA ,
IWLAGN_RTC_DATA_LOWER_BOUND ) ;
set_sec_data ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST , src ) ;
src + = get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ) ;
set_sec_offset ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ,
IWLAGN_RTC_INST_LOWER_BOUND ) ;
set_sec_data ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA , src ) ;
src + = get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ) ;
set_sec_offset ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ,
IWLAGN_RTC_DATA_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
return 0 ;
}
2012-03-07 01:30:38 +04:00
static int iwl_parse_tlv_firmware ( struct iwl_drv * drv ,
2012-03-05 23:24:50 +04:00
const struct firmware * ucode_raw ,
2012-03-11 01:00:12 +04:00
struct iwl_firmware_pieces * pieces ,
2012-03-05 23:24:50 +04:00
struct iwl_ucode_capabilities * capa )
{
struct iwl_tlv_ucode_header * ucode = ( void * ) ucode_raw - > data ;
struct iwl_ucode_tlv * tlv ;
size_t len = ucode_raw - > size ;
const u8 * data ;
u32 tlv_len ;
enum iwl_ucode_tlv_type tlv_type ;
const u8 * tlv_data ;
char buildstr [ 25 ] ;
u32 build ;
if ( len < sizeof ( * ucode ) ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " uCode has invalid length: %zd \n " , len ) ;
2012-03-05 23:24:50 +04:00
return - EINVAL ;
}
if ( ucode - > magic ! = cpu_to_le32 ( IWL_TLV_UCODE_MAGIC ) ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " invalid uCode magic: 0X%x \n " ,
2012-03-05 23:24:50 +04:00
le32_to_cpu ( ucode - > magic ) ) ;
return - EINVAL ;
}
2012-03-07 01:30:38 +04:00
drv - > fw . ucode_ver = le32_to_cpu ( ucode - > ver ) ;
2012-03-05 23:24:50 +04:00
build = le32_to_cpu ( ucode - > build ) ;
if ( build )
sprintf ( buildstr , " build %u%s " , build ,
2012-03-07 01:30:38 +04:00
( drv - > fw_index = = UCODE_EXPERIMENTAL_INDEX )
2012-03-05 23:24:50 +04:00
? " (EXP) " : " " ) ;
else
buildstr [ 0 ] = ' \0 ' ;
2012-03-07 01:30:38 +04:00
snprintf ( drv - > fw . fw_version ,
sizeof ( drv - > fw . fw_version ) ,
2012-03-05 23:24:50 +04:00
" %u.%u.%u.%u%s " ,
2012-03-07 01:30:38 +04:00
IWL_UCODE_MAJOR ( drv - > fw . ucode_ver ) ,
IWL_UCODE_MINOR ( drv - > fw . ucode_ver ) ,
IWL_UCODE_API ( drv - > fw . ucode_ver ) ,
IWL_UCODE_SERIAL ( drv - > fw . ucode_ver ) ,
2012-03-05 23:24:50 +04:00
buildstr ) ;
data = ucode - > data ;
len - = sizeof ( * ucode ) ;
while ( len > = sizeof ( * tlv ) ) {
len - = sizeof ( * tlv ) ;
tlv = ( void * ) data ;
tlv_len = le32_to_cpu ( tlv - > length ) ;
2012-03-09 12:16:35 +04:00
tlv_type = le32_to_cpu ( tlv - > type ) ;
2012-03-05 23:24:50 +04:00
tlv_data = tlv - > data ;
if ( len < tlv_len ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " invalid TLV len: %zd/%u \n " ,
2012-03-05 23:24:50 +04:00
len , tlv_len ) ;
return - EINVAL ;
}
len - = ALIGN ( tlv_len , 4 ) ;
data + = sizeof ( * tlv ) + ALIGN ( tlv_len , 4 ) ;
switch ( tlv_type ) {
case IWL_UCODE_TLV_INST :
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_INST , tlv_data ) ;
set_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_INST , tlv_len ) ;
set_sec_offset ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_INST ,
IWLAGN_RTC_INST_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_DATA :
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_DATA , tlv_data ) ;
set_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_DATA , tlv_len ) ;
set_sec_offset ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_DATA ,
IWLAGN_RTC_DATA_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_INIT :
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_INST , tlv_data ) ;
set_sec_size ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_INST , tlv_len ) ;
set_sec_offset ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_INST ,
IWLAGN_RTC_INST_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_INIT_DATA :
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_DATA , tlv_data ) ;
set_sec_size ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_DATA , tlv_len ) ;
set_sec_offset ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_DATA ,
IWLAGN_RTC_DATA_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_BOOT :
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " Found unexpected BOOT ucode \n " ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_PROBE_MAX_LEN :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
capa - > max_probe_length =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_PAN :
if ( tlv_len )
goto invalid_tlv_len ;
capa - > flags | = IWL_UCODE_TLV_FLAGS_PAN ;
break ;
case IWL_UCODE_TLV_FLAGS :
/* must be at least one u32 */
if ( tlv_len < sizeof ( u32 ) )
goto invalid_tlv_len ;
/* and a proper number of u32s */
if ( tlv_len % sizeof ( u32 ) )
goto invalid_tlv_len ;
/*
* This driver only reads the first u32 as
* right now no more features are defined ,
* if that changes then either the driver
* will not work with the new firmware , or
* it ' ll not take advantage of new features .
*/
capa - > flags = le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_INIT_EVTLOG_PTR :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
pieces - > init_evtlog_ptr =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_INIT_EVTLOG_SIZE :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
pieces - > init_evtlog_size =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_INIT_ERRLOG_PTR :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
pieces - > init_errlog_ptr =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_RUNT_EVTLOG_PTR :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
pieces - > inst_evtlog_ptr =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_RUNT_EVTLOG_SIZE :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
pieces - > inst_evtlog_size =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_RUNT_ERRLOG_PTR :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
pieces - > inst_errlog_ptr =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
case IWL_UCODE_TLV_ENHANCE_SENS_TBL :
if ( tlv_len )
goto invalid_tlv_len ;
2012-03-07 01:30:38 +04:00
drv - > fw . enhance_sensitivity_table = true ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_WOWLAN_INST :
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_WOWLAN ,
IWL_UCODE_SECTION_INST , tlv_data ) ;
set_sec_size ( pieces , IWL_UCODE_WOWLAN ,
IWL_UCODE_SECTION_INST , tlv_len ) ;
set_sec_offset ( pieces , IWL_UCODE_WOWLAN ,
IWL_UCODE_SECTION_INST ,
IWLAGN_RTC_INST_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_WOWLAN_DATA :
2012-03-11 01:00:12 +04:00
set_sec_data ( pieces , IWL_UCODE_WOWLAN ,
IWL_UCODE_SECTION_DATA , tlv_data ) ;
set_sec_size ( pieces , IWL_UCODE_WOWLAN ,
IWL_UCODE_SECTION_DATA , tlv_len ) ;
set_sec_offset ( pieces , IWL_UCODE_WOWLAN ,
IWL_UCODE_SECTION_DATA ,
IWLAGN_RTC_DATA_LOWER_BOUND ) ;
2012-03-05 23:24:50 +04:00
break ;
case IWL_UCODE_TLV_PHY_CALIBRATION_SIZE :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
capa - > standard_phy_calibration_size =
le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
2012-03-11 01:00:13 +04:00
case IWL_UCODE_TLV_SEC_RT :
iwl_store_ucode_sec ( pieces , tlv_data , IWL_UCODE_REGULAR ,
tlv_len ) ;
2012-03-11 01:00:15 +04:00
drv - > fw . mvm_fw = true ;
2012-03-11 01:00:13 +04:00
break ;
case IWL_UCODE_TLV_SEC_INIT :
iwl_store_ucode_sec ( pieces , tlv_data , IWL_UCODE_INIT ,
tlv_len ) ;
2012-03-11 01:00:15 +04:00
drv - > fw . mvm_fw = true ;
2012-03-11 01:00:13 +04:00
break ;
case IWL_UCODE_TLV_SEC_WOWLAN :
iwl_store_ucode_sec ( pieces , tlv_data , IWL_UCODE_WOWLAN ,
tlv_len ) ;
2012-03-11 01:00:15 +04:00
drv - > fw . mvm_fw = true ;
2012-03-11 01:00:13 +04:00
break ;
case IWL_UCODE_TLV_DEF_CALIB :
if ( tlv_len ! = sizeof ( struct iwl_tlv_calib_data ) )
goto invalid_tlv_len ;
if ( iwl_set_default_calib ( drv , tlv_data ) )
goto tlv_error ;
break ;
case IWL_UCODE_TLV_PHY_SKU :
if ( tlv_len ! = sizeof ( u32 ) )
goto invalid_tlv_len ;
drv - > fw . phy_config = le32_to_cpup ( ( __le32 * ) tlv_data ) ;
break ;
2012-03-05 23:24:50 +04:00
default :
2012-03-07 01:30:38 +04:00
IWL_DEBUG_INFO ( drv , " unknown TLV: %d \n " , tlv_type ) ;
2012-03-05 23:24:50 +04:00
break ;
}
}
if ( len ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " invalid TLV after parsing: %zd \n " , len ) ;
iwl_print_hex_dump ( drv , IWL_DL_FW , ( u8 * ) data , len ) ;
2012-03-05 23:24:50 +04:00
return - EINVAL ;
}
return 0 ;
invalid_tlv_len :
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " TLV %d has invalid size: %u \n " , tlv_type , tlv_len ) ;
2012-03-11 01:00:13 +04:00
tlv_error :
2012-03-07 01:30:38 +04:00
iwl_print_hex_dump ( drv , IWL_DL_FW , tlv_data , tlv_len ) ;
2012-03-05 23:24:50 +04:00
return - EINVAL ;
}
2012-03-11 01:00:14 +04:00
static int alloc_pci_desc ( struct iwl_drv * drv ,
struct iwl_firmware_pieces * pieces ,
enum iwl_ucode_type type )
{
int i ;
for ( i = 0 ;
i < IWL_UCODE_SECTION_MAX & & get_sec_size ( pieces , type , i ) ;
i + + )
if ( iwl_alloc_fw_desc ( drv , & ( drv - > fw . img [ type ] . sec [ i ] ) ,
get_sec ( pieces , type , i ) ) )
return - 1 ;
return 0 ;
}
static int validate_sec_sizes ( struct iwl_drv * drv ,
struct iwl_firmware_pieces * pieces ,
const struct iwl_cfg * cfg )
{
IWL_DEBUG_INFO ( drv , " f/w package hdr runtime inst size = %Zd \n " ,
get_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_INST ) ) ;
IWL_DEBUG_INFO ( drv , " f/w package hdr runtime data size = %Zd \n " ,
get_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_DATA ) ) ;
IWL_DEBUG_INFO ( drv , " f/w package hdr init inst size = %Zd \n " ,
get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ) ) ;
IWL_DEBUG_INFO ( drv , " f/w package hdr init data size = %Zd \n " ,
get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ) ) ;
/* Verify that uCode images will fit in card's SRAM. */
if ( get_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_INST ) >
cfg - > max_inst_size ) {
IWL_ERR ( drv , " uCode instr len %Zd too large to fit in \n " ,
get_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_INST ) ) ;
return - 1 ;
}
if ( get_sec_size ( pieces , IWL_UCODE_REGULAR , IWL_UCODE_SECTION_DATA ) >
cfg - > max_data_size ) {
IWL_ERR ( drv , " uCode data len %Zd too large to fit in \n " ,
get_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_DATA ) ) ;
return - 1 ;
}
if ( get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_INST ) >
cfg - > max_inst_size ) {
IWL_ERR ( drv , " uCode init instr len %Zd too large to fit in \n " ,
get_sec_size ( pieces , IWL_UCODE_INIT ,
IWL_UCODE_SECTION_INST ) ) ;
return - 1 ;
}
if ( get_sec_size ( pieces , IWL_UCODE_INIT , IWL_UCODE_SECTION_DATA ) >
cfg - > max_data_size ) {
IWL_ERR ( drv , " uCode init data len %Zd too large to fit in \n " ,
get_sec_size ( pieces , IWL_UCODE_REGULAR ,
IWL_UCODE_SECTION_DATA ) ) ;
return - 1 ;
}
return 0 ;
}
2012-03-05 23:24:50 +04:00
/**
* iwl_ucode_callback - callback when firmware was loaded
*
* If loaded successfully , copies the firmware into buffers
* for the card to fetch ( via DMA ) .
*/
static void iwl_ucode_callback ( const struct firmware * ucode_raw , void * context )
{
2012-03-07 01:30:38 +04:00
struct iwl_drv * drv = context ;
struct iwl_fw * fw = & drv - > fw ;
2012-03-05 23:24:50 +04:00
struct iwl_ucode_header * ucode ;
int err ;
2012-03-11 01:00:12 +04:00
struct iwl_firmware_pieces pieces ;
2012-03-22 19:51:44 +04:00
const unsigned int api_max = drv - > cfg - > ucode_api_max ;
unsigned int api_ok = drv - > cfg - > ucode_api_ok ;
const unsigned int api_min = drv - > cfg - > ucode_api_min ;
2012-03-05 23:24:50 +04:00
u32 api_ver ;
2012-03-11 01:00:14 +04:00
int i ;
2012-03-05 23:24:50 +04:00
fw - > ucode_capa . max_probe_length = 200 ;
fw - > ucode_capa . standard_phy_calibration_size =
IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE ;
if ( ! api_ok )
api_ok = api_max ;
memset ( & pieces , 0 , sizeof ( pieces ) ) ;
if ( ! ucode_raw ) {
2012-03-07 01:30:38 +04:00
if ( drv - > fw_index < = api_ok )
IWL_ERR ( drv ,
2012-03-05 23:24:50 +04:00
" request for firmware file '%s' failed. \n " ,
2012-03-07 01:30:38 +04:00
drv - > firmware_name ) ;
2012-03-05 23:24:50 +04:00
goto try_again ;
}
2012-03-07 01:30:38 +04:00
IWL_DEBUG_INFO ( drv , " Loaded firmware file '%s' (%zd bytes). \n " ,
drv - > firmware_name , ucode_raw - > size ) ;
2012-03-05 23:24:50 +04:00
/* Make sure that we got at least the API version number */
if ( ucode_raw - > size < 4 ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " File size way too small! \n " ) ;
2012-03-05 23:24:50 +04:00
goto try_again ;
}
/* Data from ucode file: header followed by uCode images */
ucode = ( struct iwl_ucode_header * ) ucode_raw - > data ;
if ( ucode - > ver )
2012-03-07 01:30:38 +04:00
err = iwl_parse_v1_v2_firmware ( drv , ucode_raw , & pieces ) ;
2012-03-05 23:24:50 +04:00
else
2012-03-07 01:30:38 +04:00
err = iwl_parse_tlv_firmware ( drv , ucode_raw , & pieces ,
2012-03-05 23:24:50 +04:00
& fw - > ucode_capa ) ;
if ( err )
goto try_again ;
2012-03-07 01:30:38 +04:00
api_ver = IWL_UCODE_API ( drv - > fw . ucode_ver ) ;
2012-03-05 23:24:50 +04:00
/*
* api_ver should match the api version forming part of the
* firmware filename . . . but we don ' t check for that and only rely
* on the API version read from firmware header from here on forward
*/
/* no api version check required for experimental uCode */
2012-03-07 01:30:38 +04:00
if ( drv - > fw_index ! = UCODE_EXPERIMENTAL_INDEX ) {
2012-03-05 23:24:50 +04:00
if ( api_ver < api_min | | api_ver > api_max ) {
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv ,
2012-03-05 23:24:50 +04:00
" Driver unable to support your firmware API. "
" Driver supports v%u, firmware is v%u. \n " ,
api_max , api_ver ) ;
goto try_again ;
}
if ( api_ver < api_ok ) {
if ( api_ok ! = api_max )
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " Firmware has old API version, "
2012-03-05 23:24:50 +04:00
" expected v%u through v%u, got v%u. \n " ,
api_ok , api_max , api_ver ) ;
else
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " Firmware has old API version, "
2012-03-05 23:24:50 +04:00
" expected v%u, got v%u. \n " ,
api_max , api_ver ) ;
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " New firmware can be obtained from "
2012-03-05 23:24:50 +04:00
" http://www.intellinuxwireless.org/. \n " ) ;
}
}
2012-03-07 01:30:38 +04:00
IWL_INFO ( drv , " loaded firmware version %s " , drv - > fw . fw_version ) ;
2012-03-05 23:24:50 +04:00
2012-03-11 01:00:15 +04:00
/*
* In mvm uCode there is no difference between data and instructions
* sections .
*/
2012-03-22 19:51:44 +04:00
if ( ! fw - > mvm_fw & & validate_sec_sizes ( drv , & pieces , drv - > cfg ) )
2012-03-05 23:24:50 +04:00
goto try_again ;
/* Allocate ucode buffers for card's bus-master loading ... */
/* Runtime instructions and 2 copies of data:
* 1 ) unmodified from disk
* 2 ) backup cache for save / restore during power - downs */
2012-03-11 01:00:14 +04:00
for ( i = 0 ; i < IWL_UCODE_TYPE_MAX ; i + + )
if ( alloc_pci_desc ( drv , & pieces , i ) )
2012-03-05 23:24:50 +04:00
goto err_pci_alloc ;
/* Now that we can no longer fail, copy information */
/*
* The ( size - 16 ) / 12 formula is based on the information recorded
* for each event , which is of mode 1 ( including timestamp ) for all
* new microcodes that include this information .
*/
2012-03-07 01:30:37 +04:00
fw - > init_evtlog_ptr = pieces . init_evtlog_ptr ;
2012-03-05 23:24:50 +04:00
if ( pieces . init_evtlog_size )
2012-03-07 01:30:37 +04:00
fw - > init_evtlog_size = ( pieces . init_evtlog_size - 16 ) / 12 ;
2012-03-05 23:24:50 +04:00
else
2012-03-07 01:30:37 +04:00
fw - > init_evtlog_size =
2012-03-22 19:51:44 +04:00
drv - > cfg - > base_params - > max_event_log_size ;
2012-03-07 01:30:37 +04:00
fw - > init_errlog_ptr = pieces . init_errlog_ptr ;
fw - > inst_evtlog_ptr = pieces . inst_evtlog_ptr ;
2012-03-05 23:24:50 +04:00
if ( pieces . inst_evtlog_size )
2012-03-07 01:30:37 +04:00
fw - > inst_evtlog_size = ( pieces . inst_evtlog_size - 16 ) / 12 ;
2012-03-05 23:24:50 +04:00
else
2012-03-07 01:30:37 +04:00
fw - > inst_evtlog_size =
2012-03-22 19:51:44 +04:00
drv - > cfg - > base_params - > max_event_log_size ;
2012-03-07 01:30:37 +04:00
fw - > inst_errlog_ptr = pieces . inst_errlog_ptr ;
2012-03-05 23:24:50 +04:00
/*
* figure out the offset of chain noise reset and gain commands
* base on the size of standard phy calibration commands table size
*/
if ( fw - > ucode_capa . standard_phy_calibration_size >
IWL_MAX_PHY_CALIBRATE_TBL_SIZE )
fw - > ucode_capa . standard_phy_calibration_size =
IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE ;
/* We have our copies now, allow OS release its copies */
release_firmware ( ucode_raw ) ;
2012-03-07 01:30:38 +04:00
complete ( & drv - > request_firmware_complete ) ;
2012-03-05 23:24:50 +04:00
2012-03-22 19:51:44 +04:00
drv - > op_mode = iwl_dvm_ops . start ( drv - > trans , drv - > cfg , & drv - > fw ) ;
2012-03-05 23:24:50 +04:00
2012-03-07 01:30:38 +04:00
if ( ! drv - > op_mode )
2012-03-05 23:24:50 +04:00
goto out_unbind ;
return ;
try_again :
/* try next, if any */
release_firmware ( ucode_raw ) ;
2012-03-07 01:30:38 +04:00
if ( iwl_request_firmware ( drv , false ) )
2012-03-05 23:24:50 +04:00
goto out_unbind ;
return ;
err_pci_alloc :
2012-03-07 01:30:38 +04:00
IWL_ERR ( drv , " failed to allocate pci memory \n " ) ;
iwl_dealloc_ucode ( drv ) ;
2012-03-05 23:24:50 +04:00
release_firmware ( ucode_raw ) ;
out_unbind :
2012-03-07 01:30:38 +04:00
complete ( & drv - > request_firmware_complete ) ;
2012-03-22 19:51:44 +04:00
device_release_driver ( drv - > trans - > dev ) ;
2012-03-05 23:24:50 +04:00
}
2012-03-26 19:51:09 +04:00
struct iwl_drv * iwl_drv_start ( struct iwl_shared * shrd ,
struct iwl_trans * trans ,
const struct iwl_cfg * cfg )
2012-02-07 16:18:40 +04:00
{
2012-03-07 01:30:38 +04:00
struct iwl_drv * drv ;
2012-02-07 16:18:40 +04:00
int ret ;
2012-03-07 01:30:38 +04:00
drv = kzalloc ( sizeof ( * drv ) , GFP_KERNEL ) ;
if ( ! drv ) {
dev_printk ( KERN_ERR , trans - > dev , " Couldn't allocate iwl_drv " ) ;
2012-03-26 19:51:09 +04:00
return NULL ;
2012-02-07 16:18:40 +04:00
}
2012-03-26 19:51:09 +04:00
/* For printing only - temporary until we change the logger */
2012-03-07 01:30:38 +04:00
drv - > shrd = shrd ;
2012-03-26 19:51:09 +04:00
drv - > trans = trans ;
2012-03-22 19:51:44 +04:00
drv - > cfg = cfg ;
2012-02-07 16:18:40 +04:00
2012-03-07 01:30:38 +04:00
init_completion ( & drv - > request_firmware_complete ) ;
2012-02-07 16:18:40 +04:00
2012-03-07 01:30:38 +04:00
ret = iwl_request_firmware ( drv , true ) ;
2012-02-07 16:18:40 +04:00
if ( ret ) {
dev_printk ( KERN_ERR , trans - > dev , " Couldn't request the fw " ) ;
2012-03-07 01:30:38 +04:00
kfree ( drv ) ;
2012-03-26 19:51:09 +04:00
drv = NULL ;
2012-02-07 16:18:40 +04:00
}
2012-03-26 19:51:09 +04:00
return drv ;
2012-02-07 16:18:40 +04:00
}
2012-03-26 19:51:09 +04:00
void iwl_drv_stop ( struct iwl_drv * drv )
2012-02-07 16:27:31 +04:00
{
2012-03-07 01:30:38 +04:00
wait_for_completion ( & drv - > request_firmware_complete ) ;
2012-03-05 23:24:51 +04:00
2012-02-09 18:08:15 +04:00
/* op_mode can be NULL if its start failed */
2012-03-07 01:30:38 +04:00
if ( drv - > op_mode )
iwl_op_mode_stop ( drv - > op_mode ) ;
2012-02-07 16:27:31 +04:00
2012-03-07 01:30:38 +04:00
iwl_dealloc_ucode ( drv ) ;
2012-03-05 23:24:48 +04:00
2012-03-07 01:30:38 +04:00
kfree ( drv ) ;
2012-02-07 16:27:31 +04:00
}