/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright 2020 Broadcom Ltd. */ #include #include #include #include #include #include #include #include "u-boot/rsa.h" #include "u-boot/rsa-mod-exp.h" #include #include #include "bcm_secure.h" #include "bcm_otp.h" #include /* callback can be called early in before relocation and * before bss zero loop */ static bcm_sec_t s_bcm_sec __attribute__((section(".data"))) = {0}; static inline void copy_append(bcm_sec_ctrl_arg_t* d, bcm_sec_ctrl_arg_t* s, u32 len) { int i,j; for(i = 0; i < len; i++) { if (!s[i].ctrl) { continue; } for(j = 0; j < len; j++) { if ( (u32)d[j].ctrl == (u32)s[i].ctrl || !((u32)d[j].ctrl) ) { d[j].ctrl = s[i].ctrl; d[j].ctrl_arg = s[i].ctrl_arg; break; } } } } static void bcm_sec_set_args(bcm_sec_t *sec, bcm_sec_cb_arg_t args[SEC_CTRL_ARG_MAX]) { int i; for (i = 0; i < SEC_CTRL_ARG_MAX; i++) { copy_append(sec->cb[i].arg, args[i].arg, SEC_CTRL_RUN_ORDER_MAX); } } static bcm_sec_ctrl_arg_t* bcm_sec_get_ctrl_arg(bcm_sec_t* sec, bcm_sec_ctrl_arg_t* ctrl, bcm_sec_ctrl_arg_num_t ctrl_id) { int i; bcm_sec_ctrl_arg_t *c; bcm_sec_ctrl_arg_t *res = NULL; c = sec->cb[ctrl_id].arg; for(i = 0; i < SEC_CTRL_RUN_ORDER_MAX; i++) { if (!c[i].ctrl) { continue; } if ( (u32)c[i].ctrl == (u32)ctrl->ctrl) { res = &c[i]; break; } } return res; } int bcm_sec_update_ctrl_arg(bcm_sec_ctrl_arg_t* arg, bcm_sec_ctrl_arg_num_t ctrl) { bcm_sec_ctrl_arg_t *_arg = bcm_sec_get_ctrl_arg(bcm_sec(), arg, ctrl); if (!_arg) { return -1; } if (ctrl == SEC_CTRL_ARG_KEY && ( (_arg->ctrl == SEC_CTRL_KEY_CHAIN_AES) || (_arg->ctrl == SEC_CTRL_KEY_CHAIN_ENCKEY) || (_arg->ctrl == SEC_CTRL_KEY_EXPORT_ITEM)) ) { ((bcm_sec_key_arg_t*)_arg->ctrl_arg)->arg = arg->ctrl_arg; } else { _arg->ctrl_arg = arg->ctrl_arg; } return 0; } /* based on the content of theotp field returns state - unsec, mfg or fld*/ static int bcm_sec_boot_state(bcm_sec_state_t* sec_state) { otp_hw_ctl_data_t ctl_data = { .addr = SOTP_MAP_FLD_ROE, .status = OTP_HW_CMN_STATUS_ROW_DATA_VALID }; otp_hw_cmn_ctl_cmd_t cmd = { .ctl = OTP_HW_CMN_CTL_STATUS , .data = (uintptr_t)&ctl_data, .size = sizeof(ctl_data) }; int rc = -1; u32 *btrm_en = NULL, *btrm_cust_en = NULL, *cust_mid = NULL, size = 0, res = 0; *sec_state = SEC_STATE_UNSEC; if (bcm_otp_read(OTP_MAP_CUST_BTRM_BOOT_ENABLE, &btrm_cust_en, &size) || bcm_otp_read(OTP_MAP_BRCM_BTRM_BOOT_ENABLE, &btrm_en, &size) || bcm_otp_read(OTP_MAP_CUST_MFG_MRKTID, &cust_mid, &size)) { goto err; } if ( !(*btrm_en) || !(*btrm_cust_en)) { goto done; } *sec_state = SEC_STATE_GEN3_MFG; if ( !(*cust_mid) ) { goto done; } if (bcm_otp_ctl(BCM_SOTP_MAP, &cmd, &res)) { goto err; } if (!(res&OTP_HW_CMN_STATUS_ROW_DATA_VALID) ) { /*printf("%s ek: 0x%x \n",__FUNCTION__,*(u32*)BCM_SECBT_CRED_AES);*/ if (!*((u32*)BCM_SECBT_CRED_AES)) { goto done; } } res = 0; ctl_data.addr = SOTP_MAP_FLD_HMID; if (bcm_otp_ctl(BCM_SOTP_MAP, &cmd, &res)) { goto err; } if (!(res&OTP_HW_CMN_STATUS_ROW_DATA_VALID)) { /*printf("%s pub: 0x%x \n",__FUNCTION__,*(u32*)BCM_SECBT_CRED_MOD);*/ if (! *((u32*)BCM_SECBT_CRED_MOD)) { goto done; } } *sec_state = SEC_STATE_GEN3_FLD; done: rc = 0; err: return rc; } int bcm_sec_set_sec_ser_num( char * ser_num, u32 ser_num_size) { int rc = -1; /* Commit the new secure serial number to S/OTP */ rc = bcm_otp_write(SOTP_MAP_SER_NUM, ser_num, ser_num_size); if(rc) { printf("%s: ERROR! Failed to commit secure serial number! rc:%d\n", __FUNCTION__, rc); } return rc; } int bcm_sec_get_sec_ser_num( char * ser_num, u32 ser_num_size) { int rc = -1; u32 * pdata = NULL; u32 size = 0; u32 num_words = 0; /* Get secure serial number fields from S/OTP */ rc = bcm_otp_read(SOTP_MAP_SER_NUM, &pdata, &size); if( rc == OTP_HW_CMN_ERR_KEY_EMPTY ) { memset(ser_num, 0x0, ser_num_size); return OTP_HW_CMN_OK; } /* If retrieval fails, check dtb for exported value */ if( rc ) { int node; node = fdt_path_offset(gd->fdt_blob, "/trust/serial_num"); if (node >= 0) { char * cp = NULL; cp = (char *)fdt_getprop(gd->fdt_blob, node, "value", &size); if (cp) { memcpy(ser_num, cp, (size>ser_num_size?ser_num_size:size)); rc = 0; } } if(rc) { printf("%s: ERROR! Failed to retrieve secure serial number! rc:%d\n", __FUNCTION__, rc); } } else { memcpy(ser_num, pdata, (size>ser_num_size?ser_num_size:size)); } return rc; } int bcm_sec_set_dev_spec_key( char * dev_spec_key, u32 dev_spec_key_size) { int rc = -1; /* Commit the new device specific key to S/OTP */ rc = bcm_otp_write(SOTP_MAP_KEY_DEV_SPECIFIC, dev_spec_key, dev_spec_key_size); if(rc) { printf("%s: ERROR! Failed to commit secure serial number! rc:%d\n", __FUNCTION__, rc); } return rc; } int bcm_sec_get_dev_spec_key( char * dev_spec_key, u32 dev_spec_key_size) { int rc = -1; u32 * pdata = NULL; u32 size = 0; u32 num_words = 0; /* Get device specific from S/OTP */ rc = bcm_otp_read(SOTP_MAP_KEY_DEV_SPECIFIC, &pdata, &size); if( rc == OTP_HW_CMN_ERR_KEY_EMPTY ) { memset(dev_spec_key, 0x0, dev_spec_key_size); return OTP_HW_CMN_OK; } if(rc) { printf("%s: ERROR! Failed to retrieve secure serial number! rc:%d\n", __FUNCTION__, rc); } else { memcpy(dev_spec_key, pdata, (size>dev_spec_key_size?dev_spec_key_size:size)); } return rc; } #define ANTI_ROLLBACK_LVL_NUMBITS 2 #define ANTI_ROLLBACK_LVL_MASK ((1 << ANTI_ROLLBACK_LVL_NUMBITS)-1) #define ANTI_ROLLBACK_LVL_SHIFT ANTI_ROLLBACK_LVL_NUMBITS static int bcm_sec_antirollback_lvl_ctl( u32 * lvl, int write ) { int rc = -1; u32 * pdata = NULL; u32 size = 0; u32 current_lvl = 0; u32 num_lvls_word = sizeof(u32)*8/ANTI_ROLLBACK_LVL_NUMBITS; u32 num_words = 0; u32 i,j,tmp_lvl; /* Get anti_rollback fields from S/OTP */ rc = bcm_otp_read(SOTP_MAP_ANTI_ROLLBACK, &pdata, &size); if (rc) { printf("%s: ERROR! Failed to retrieve anti-rollback level data! rc:%d\n", __FUNCTION__, rc); goto finish_read; } /* Calculate level */ num_words = size/sizeof(u32); for( i=0; i num_words * num_lvls_word ) { //FIXME BOUNDARY printf("%s: ERROR! Requested anti-rollback level %d is higher than maximum %d\n", __FUNCTION__, tmp_lvl, num_words * num_lvls_word ); rc = -1; goto finish; } /* Encode new rollback level */ for( i=0; i= num_lvls_word ) { /* adjust level */ tmp_lvl -= num_lvls_word; /* If current word is filled continue to next word */ if( pdata[i] == 0xFFFFFFFF ) continue; else pdata[i] = 0xFFFFFFFF; } else { /* Selectively fill the row according to level */ while( tmp_lvl ) { pdata[i] |= ANTI_ROLLBACK_LVL_MASK << ((tmp_lvl-1)*ANTI_ROLLBACK_LVL_SHIFT); tmp_lvl--; } } if( tmp_lvl == 0 ) break; } /* Commit the new anti-rollback level to S/OTP */ rc = bcm_otp_write(SOTP_MAP_ANTI_ROLLBACK, pdata, size); if(rc) { printf("%s: ERROR! Failed to commit anti-rollback level data! rc:%d\n", __FUNCTION__, rc); goto finish; } } else { *lvl = current_lvl; } finish: return rc; } int bcm_sec_get_antirollback_lvl( u32 * lvl) { return(bcm_sec_antirollback_lvl_ctl( lvl, 0 )); } int bcm_sec_set_antirollback_lvl( u32 lvl) { return(bcm_sec_antirollback_lvl_ctl( &lvl, 1 )); } /* assigns a number to a callback to be run ascending order in bcm_sec_do*/ static void bcm_set_cb_order(bcm_sec_ctrl_arg_num_t ord_arg[SEC_CTRL_ARG_MAX]) { bcm_sec_ctrl_arg_num_t *ord = bcm_sec()->ord; if (ord_arg) { memcpy(ord, ord_arg, SEC_CTRL_ARG_MAX*sizeof(bcm_sec_ctrl_arg_num_t)); } else { int i; for (i = 0; i < SEC_CTRL_ARG_MAX; i++) { ord[i] = i; } } } bcm_sec_t* bcm_sec(void) { return &s_bcm_sec; } bcm_sec_state_t bcm_sec_state(void) { return bcm_sec()->state; } void bcm_sec_get_active_aes_key(u8** key) { *key = bcm_sec()->key.ek; } void bcm_sec_set_active_aes_key(u8* key) { bcm_sec()->key.ek = key; } u8* bcm_sec_get_active_pub_key(void) { return bcm_sec()->key.pub; } u8* bcm_sec_set_active_pub_key(u8 * key) { bcm_sec()->key.pub = key; } __weak void bcm_sec_clean_secmem(bcm_sec_t* sec) { } __weak int bcm_sec_btrm_key_info(bcm_sec_t* sec) { /* Set root keys */ memcpy(sec->key.rsa_pub, (void*)BCM_SECBT_CRED_MOD, RSA2048_BYTES); memcpy(sec->key.aes_ek, (void*)BCM_SECBT_CRED_AES, BCM_SECBT_AES_CBC128_EK_LEN); memcpy(sec->key.aes_ek + BCM_SECBT_AES_CBC128_EK_LEN, (void*)BCM_SECBT_CRED_AES_IV, BCM_SECBT_AES_CBC128_EK_LEN); /* Set active keys */ sec->key.pub = sec->key.rsa_pub; sec->key.ek = sec->key.aes_ek; /* Clear delegation config */ sec->delg_cfg_obj = NULL; return 0; } void bcm_sec_clean_keys(bcm_sec_t* sec) { if (sec->key.ek) { memset(sec->key.ek, 0, BCM_SECBT_AES_CBC128_EK_LEN*2); } memset(&sec->key, 0, sizeof(bcm_sec_key_t)); } /* should be overridden by the implementation */ __weak void bcm_sec_cb_init(bcm_sec_t* obj) { } __weak void bcm_sec_init(void) { bcm_sec_t* sec = bcm_sec(); bcm_sec_cb_init(sec); bcm_set_cb_order(NULL); bcm_sec_boot_state(&sec->state); switch (sec->state) { case SEC_STATE_GEN3_MFG: printf("Board is MFG secure\n"); break; case SEC_STATE_GEN3_FLD: printf("Board is FLD secure\n"); break; default: printf("Board is non secure\n"); } } __weak int bcm_sec_do(bcm_sec_ctx_t ctx, bcm_sec_cb_arg_t arg[SEC_CTRL_ARG_MAX]) { int rc = -1, i, j; bcm_sec_t *sec = bcm_sec(); bcm_sec_ctrl_cb_t *__cb; bcm_sec_ctrl_arg_t *cb_arg; if (arg) { bcm_sec_set_args(sec, arg); } /* This is a 2 step function. eg SEC_LEVEL_NONE->SEC_LEVEL_INIT->FINAL LEVEL(see below) . */ if (ctx & SEC_SCHED_CLR) { sec->sched_ctx = SEC_NONE; return rc; } if (sec->sched_ctx & SEC_SET_SCHED) { /* continuing with schedule level*/ ctx = (sec->sched_ctx & (~SEC_SET_SCHED)); } else if (ctx & SEC_SET_SCHED) { sec->sched_ctx = ctx; //debug("SEC *** SCHEDULED \n"); return rc; } for (j = 0; j < SEC_CTRL_ARG_MAX; j++) { __cb = &sec->cb[sec->ord[j]]; if (!__cb->cb) { continue; } cb_arg = __cb->arg; for (i = 0; i < SEC_CTRL_RUN_ORDER_MAX; i++) { #if DEBUG if (cb_arg[i].ctrl) { printf("SEC *** DO %d CTRL %d parm 0x%x\n", j, cb_arg[i].ctrl, cb_arg[i].ctrl_arg); } #endif if (__cb->cb(sec, cb_arg[i].ctrl, cb_arg[i].ctrl_arg)) { cb_arg[i].ctrl = 0; cb_arg[i].ctrl_arg = NULL; goto err; } cb_arg[i].ctrl = 0; cb_arg[i].ctrl_arg = NULL; } } sec->curr_ctx = ctx; rc = 0; err: return rc; } void bcm_sec_deinit(void) { bcm_sec_clean_secmem(&s_bcm_sec); bcm_sec_clean_keys(&s_bcm_sec); memset(&s_bcm_sec, 0, sizeof(bcm_sec_t)); } void bcm_sec_abort(void) { bcm_sec_deinit(); WATCHDOG_RESET(); /* we never return if watchdog wasn't enabled */ hang(); }