blob: d1bb539a201b2e05494082f56916affb43d075e3 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Key manager top level
//
`include "prim_assert.sv"
module keymgr_ctrl import keymgr_pkg::*;(
input clk_i,
input rst_ni,
// lifecycle enforcement
input en_i,
// Software interface
input init_i,
output logic init_done_o,
input op_start_i,
input keymgr_ops_e op_i,
output logic op_done_o,
output keymgr_op_status_e status_o,
output logic [ErrLastPos-1:0] error_o,
output logic data_valid_o,
output logic wipe_key_o,
output keymgr_working_state_e working_state_o,
// Data input
input otp_ctrl_pkg::otp_keymgr_key_t root_key_i,
output keymgr_gen_out_e hw_sel_o,
output keymgr_stage_e stage_sel_o,
// KMAC ctrl interface
output logic adv_en_o,
output logic id_en_o,
output logic gen_en_o,
output logic [Shares-1:0][KeyWidth-1:0] key_o,
output logic load_key_o,
input kmac_done_i,
input kmac_input_invalid_i, // asserted when selected data fails criteria check
input kmac_fsm_err_i, // asserted when kmac fsm reaches unexpected state
input kmac_op_err_i, // asserted when kmac itself reports an error
input kmac_cmd_err_i, // asserted when more than one command given to kmac
input [Shares-1:0][KeyWidth-1:0] kmac_data_i,
// prng control interface
input [(LfsrWidth/2)-1:0] entropy_i,
input prng_reseed_ack_i,
output logic prng_reseed_req_o,
output logic prng_en_o
);
localparam int EntropyWidth = LfsrWidth / 2;
localparam int EntropyRounds = KeyWidth / EntropyWidth;
localparam int CntWidth = $clog2(EntropyRounds + 1);
keymgr_working_state_e state_q, state_d;
logic [Shares-1:0][EntropyRounds-1:0][EntropyWidth-1:0] key_state_q, key_state_d;
logic [CntWidth-1:0] cnt;
logic cnt_en;
logic cnt_clr;
logic data_valid;
logic adv_en_q;
logic op_accepted;
logic invalid_op;
// disable is treated like an advanced call
logic advance_sel;
logic disable_sel;
logic gen_id_sel;
logic gen_out_sw_sel;
logic gen_out_hw_sel;
logic gen_out_sel;
logic gen_sel;
// something went wrong with the kmac interface operation
logic kmac_op_err;
assign advance_sel = op_start_i & op_i == OpAdvance & en_i;
assign gen_id_sel = op_start_i & op_i == OpGenId & en_i;
assign gen_out_sw_sel = op_start_i & op_i == OpGenSwOut & en_i;
assign gen_out_hw_sel = op_start_i & op_i == OpGenHwOut & en_i;
assign gen_out_sel = gen_out_sw_sel | gen_out_hw_sel;
assign gen_sel = gen_id_sel | gen_out_sel;
// disable is selected whenever a normal operation is not, and when
// keymgr is disabled
assign disable_sel = (op_start_i & !(gen_sel | advance_sel)) |
!en_i;
assign adv_en_o = op_accepted & (advance_sel | disable_sel);
assign id_en_o = op_accepted & gen_id_sel;
assign gen_en_o = op_accepted & gen_out_sel;
assign load_key_o = adv_en_o & !adv_en_q;
// check incoming kmac data validity
// also check inputs used during compute
assign data_valid = valid_data_chk(kmac_data_i[0]) & valid_data_chk(kmac_data_i[1])
& !kmac_input_invalid_i & !kmac_op_err;
// Unlike the key state, the working state can be safely reset.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
state_q <= StReset;
adv_en_q <= 1'b0;
end else begin
state_q <= state_d;
adv_en_q <= adv_en_o;
end
end
// prevents unknowns from reaching the outside world.
// - whatever operation causes the input data select to be disabled should not expose the key
// state.
// - when there are no operations, the key state also should be exposed.
assign key_o = (~op_start_i | disable_sel) ? {EntropyRounds * Shares {entropy_i}} : key_state_q;
// key state is intentionally not reset
always_ff @(posedge clk_i) begin
key_state_q <= key_state_d;
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cnt <= '0;
end else if (cnt_clr) begin
cnt <= '0;
end else if (cnt_en) begin
cnt <= cnt + 1'b1;
end
end
assign kmac_op_err = kmac_cmd_err_i | kmac_fsm_err_i | kmac_op_err_i;
// root key valid sync
logic root_key_valid_q;
prim_flop_2sync # (
.Width(1)
) u_key_valid_sync (
.clk_i,
.rst_ni,
.d_i(root_key_i.valid),
.q_o(root_key_valid_q)
);
always_comb begin
// persistent data
state_d = state_q;
key_state_d = key_state_q;
// locally consumed select signals
cnt_en = 1'b0;
cnt_clr = 1'b0;
op_accepted = 1'b0;
invalid_op = 1'b0;
// data update and select signals
hw_sel_o = HwKey;
stage_sel_o = Disable;
// enable prng toggling
prng_reseed_req_o = 1'b0;
prng_en_o = 1'b0;
op_done_o = 1'b0;
init_done_o = 1'b1;
wipe_key_o = 1'b0;
unique case (state_q)
// This state does not accept any command. Issuing any command
// will cause an immediate error
StReset: begin
init_done_o = 1'b0;
// in reset state, don't enable entropy yet, since there are no users.
// long term, this should be replaced by a req/ack with csrng
prng_en_o = 1'b0;
op_done_o = op_start_i;
invalid_op = op_start_i;
// When initialization command is given, begin.
// Note, if init is called at the same time as start, it is considered
// an invalid command sequence.
if (init_i && !invalid_op && en_i) begin
state_d = StEntropyReseed;
end else begin
state_d = StReset;
init_done_o = init_i & invalid_op;
end
end
// reseed entropy
StEntropyReseed: begin
prng_reseed_req_o = 1'b1;
if (prng_reseed_ack_i) begin
state_d = StRandom;
end
end
// This state does not accept any command.
StRandom: begin
init_done_o = 1'b0;
prng_en_o = 1'b1;
// populate both shares with the same entropy
// This is the default mask
if (cnt < EntropyRounds) begin
cnt_en = 1'b1;
for (int i = 0; i < Shares; i++) begin
key_state_d[i][cnt] = entropy_i;
end
end
// when mask population is complete, xor the root_key into the zero share
// if in the future the root key is updated to 2 shares, it will direclty overwrite
// the values here
else begin
cnt_clr = 1'b1;
// absorb key if valid, otherwise use entropy
if (root_key_valid_q) begin
key_state_d[0] = root_key_i.key_share0;
key_state_d[1] = root_key_i.key_share1;
end
init_done_o = 1'b1;
state_d = StInit;
end
end
// Beginning from the Init state, operations are accepted.
// Only valid operation is advance state. If invalid command received,
// random data is selected for operation and no persistent state is changed.
StInit: begin
op_done_o = op_start_i & kmac_done_i;
op_accepted = op_start_i;
// when advancing select creator data, otherwise use random input
stage_sel_o = advance_sel ? Creator : Disable;
hw_sel_o = gen_out_hw_sel ? HwKey : SwKey;
// key state is updated when it is an advance call
if (!en_i) begin
state_d = StWipe;
end else if (op_done_o && (disable_sel || kmac_op_err)) begin
key_state_d = kmac_data_i;
state_d = StDisabled;
end else if (op_done_o && advance_sel) begin
key_state_d = data_valid ? kmac_data_i : key_state_q;
state_d = StCreatorRootKey;
end else if (op_done_o) begin
invalid_op = 1'b1;
end
end
// all commands are valid during this stage
StCreatorRootKey: begin
op_done_o = op_start_i & kmac_done_i;
op_accepted = op_start_i;
// when generating, select creator data input
// when advancing, select owner intermediate key as target
// when disabling, select random data input
stage_sel_o = disable_sel ? Disable :
advance_sel ? OwnerInt : Creator;
hw_sel_o = gen_out_hw_sel ? HwKey : SwKey;
// key state is updated when it is an advance call
if (!en_i) begin
state_d = StWipe;
end else if (op_done_o && (disable_sel || kmac_op_err)) begin
key_state_d = kmac_data_i;
state_d = StDisabled;
end else if (op_done_o && advance_sel) begin
key_state_d = data_valid ? kmac_data_i : key_state_q;
state_d = StOwnerIntKey;
end
end
// all commands are valid during this stage
StOwnerIntKey: begin
op_done_o = op_start_i & kmac_done_i;
op_accepted = op_start_i;
// when generating, select owner intermediate data input
// when advancing, select owner as target
// when disabling, select random data input
stage_sel_o = disable_sel ? Disable :
advance_sel ? Owner : OwnerInt;
hw_sel_o = gen_out_hw_sel ? HwKey : SwKey;
if (!en_i) begin
state_d = StWipe;
end else if (op_done_o && (disable_sel || kmac_op_err)) begin
key_state_d = kmac_data_i;
state_d = StDisabled;
end else if (op_done_o && advance_sel) begin
key_state_d = data_valid ? kmac_data_i : key_state_q;
state_d = StOwnerKey;
end
end
// all commands are valid during this stage
// however advance goes directly to disabled state
StOwnerKey: begin
op_done_o = op_start_i & kmac_done_i;
op_accepted = op_start_i;
// when generating, select owner data input
// when advancing, select disable as target
// when disabling, select random data input
stage_sel_o = disable_sel | advance_sel ? Disable : Owner;
hw_sel_o = gen_out_hw_sel ? HwKey : SwKey;
// Calling advanced from ownerKey also leads to disable
// Thus data_valid is not checked
if (!en_i) begin
state_d = StWipe;
end else if (op_done_o && (advance_sel || disable_sel || kmac_op_err)) begin
key_state_d = kmac_data_i;
state_d = StDisabled;
end
end
// The wipe state immediately clears out the key state, but waits for any ongoing
// transaction to finish before going to disabled state.
// Unlike the random state, this is an immedaite shutdown request, so all parts of the
// key are wiped.
StWipe: begin
stage_sel_o = Disable;
hw_sel_o = HwKey;
op_done_o = op_start_i & kmac_done_i;
op_accepted = op_start_i;
wipe_key_o = 1'b1;
for (int i = 0; i < Shares; i++) begin
key_state_d[i] = {EntropyRounds{entropy_i}};
end
// If the enable is dropped during the middle of a transaction, we clear and wait for that
// transaction to gracefully complete (if it can).
// There are two scenarios:
// 1. the operation completed right when we started wiping, in which case the done would
// clear the start.
// 2. the operation completed before we started wiping, or there was never an operation to
// begin with (op_start_i == 0), in this case, don't wait and immediately transition
if (!op_start_i) begin
state_d = StDisabled;
end
end
// Terminal state (StDisabled is included)
// However, it will continue to kick off dummy transactions
default: begin
op_done_o = op_start_i & kmac_done_i;
// accept any command, but always select fake data
op_accepted = op_start_i;
stage_sel_o = Disable;
hw_sel_o = gen_out_hw_sel ? HwKey : SwKey;
// During disabled state, continue to update state
key_state_d = (op_done_o && advance_sel) ? kmac_data_i : key_state_q;
// Despite accepting all commands, operations are always
// considered invalid in disabled state
// TBD this may be changed later if we decide to hide disable state from
// software.
invalid_op = op_start_i;
end
endcase // unique case (state_q)
end
// Current working state provided for software read
assign working_state_o = state_q;
// if operation was never accepted (ie a generate was called in StReset / StRandom), then
// never update the sw / hw outputs when operation is complete
assign data_valid_o = op_done_o & op_accepted & data_valid & gen_sel;
// data errors are not relevant when operation was not accepted.
assign error_o[ErrInvalidOp] = invalid_op;
assign error_o[ErrInvalidCmd] = op_start_i & op_accepted & kmac_op_err;
assign error_o[ErrInvalidIn] = op_done_o & op_accepted & kmac_input_invalid_i;
assign error_o[ErrInvalidOut] = op_done_o & op_accepted & ~data_valid;
always_comb begin
status_o = OpIdle;
if (op_done_o) begin
status_o = |error_o ? OpDoneFail : OpDoneSuccess;
end else if (op_start_i) begin
status_o = OpWip;
end
end
///////////////////////////////
// Functions
///////////////////////////////
// unclear what this is supposed to be yet
// right now just check to see if it not all 0's and not all 1's
function automatic logic valid_data_chk (logic [KeyWidth-1:0] value);
return |value & ~&value;
endfunction // byte_mask
endmodule