blob: 4fd925163086c0e3c7d34b353b3cb315dbad457c [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use crate::otp::alert_handler_regs::*;
use crate::otp::lc_state::LcStateVal;
use crate::otp::otp_img::OtpRead;
use anyhow::{bail, Result};
use bitvec::prelude::*;
use crc::{Crc, Digest};
use num_enum::TryFromPrimitive;
use std::convert::TryFrom;
/// ALERT_HANDLER_ALERT_CLASS related register values.
#[derive(Clone, Copy, Debug, PartialEq)]
struct AlertClassRegs {
regwen: u32,
ctrl: u32,
accum_thresh: u32,
timeout_cyc: u32,
phase_cycs: [u32; ALERT_HANDLER_PARAM_N_PHASES as usize],
}
/// Register values for alert_handler used in CRC32 calculation.
#[derive(Debug, PartialEq)]
pub struct AlertRegs {
/// ALERT_HANDLER_LOC_ALERT_REGWEN
regwen: [u32; ALERT_HANDLER_ALERT_REGWEN_MULTIREG_COUNT as usize],
/// ALERT_HANDLER_ALERT_EN_SHADOWED
en: [u32; ALERT_HANDLER_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
/// ALERT_HANDLER_ALERT_CLASS_SHADOWED
class: [u32; ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
/// ALERT_HANDLER_LOC_ALERT_REGWEN
loc_regwen: [u32; ALERT_HANDLER_LOC_ALERT_REGWEN_MULTIREG_COUNT as usize],
/// ALERT_HANDLER_LOC_ALERT_EN_SHADOWED
loc_en: [u32; ALERT_HANDLER_LOC_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
/// ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED
loc_class: [u32; ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
/// Alert handler class registers
class_regs: [AlertClassRegs; ALERT_HANDLER_PARAM_N_CLASSES as usize],
}
// TODO: Use bindgen to produce the following enum definitions.
/// Alert classification values.
///
/// Based on values generated by sparse-fsm-encode.py and defined in
/// sw/device/silicon_creator/lib/drivers/alert.h as alert_class_t.
#[derive(TryFromPrimitive)]
#[repr(u8)]
enum AlertClass {
X = 0x94,
A = 0xee,
B = 0x64,
C = 0xa7,
D = 0x32,
}
impl AlertClass {
fn index(&self) -> usize {
match self {
AlertClass::A => 0,
AlertClass::B => 1,
AlertClass::C => 2,
AlertClass::D => 3,
AlertClass::X => 0,
}
}
fn from_index(index: usize) -> Self {
match index {
0 => AlertClass::A,
1 => AlertClass::B,
2 => AlertClass::C,
3 => AlertClass::D,
_ => AlertClass::X,
}
}
}
#[derive(TryFromPrimitive)]
#[repr(u8)]
enum AlertEnable {
None = 0xa9,
Enabled = 0x07,
Locked = 0xd2,
}
#[derive(TryFromPrimitive)]
#[repr(u8)]
enum AlertEscalate {
None = 0xd1,
Phase0 = 0xb9,
Phase1 = 0xcb,
Phase2 = 0x25,
Phase3 = 0x76,
}
struct AlertClassConfig {
enabled: AlertEnable,
escalate: AlertEscalate,
accum_thresh: u32,
timeout_cyc: u32,
phase_cycs: [u32; ALERT_HANDLER_PARAM_N_PHASES as usize],
}
impl Default for AlertClassRegs {
fn default() -> Self {
AlertClassRegs {
regwen: 1,
ctrl: 0,
accum_thresh: 0,
timeout_cyc: 0,
phase_cycs: [0; ALERT_HANDLER_PARAM_N_PHASES as usize],
}
}
}
impl Default for AlertRegs {
fn default() -> Self {
AlertRegs {
regwen: [1; ALERT_HANDLER_ALERT_REGWEN_MULTIREG_COUNT as usize],
loc_regwen: [1; ALERT_HANDLER_LOC_ALERT_REGWEN_MULTIREG_COUNT as usize],
en: [0; ALERT_HANDLER_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
class: [0; ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
loc_en: [0; ALERT_HANDLER_LOC_ALERT_EN_SHADOWED_MULTIREG_COUNT as usize],
loc_class: [0; ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize],
class_regs: [Default::default(); ALERT_HANDLER_PARAM_N_CLASSES as usize],
}
}
}
impl AlertRegs {
/// Compute the CRC32 of the internal register values to match the value produced by
/// `sw/device/silicon_creator/lib/drivers/alert.h:alert_config_crc32`.
pub fn crc32(self) -> u32 {
let crc = new_crc();
let mut digest = crc.digest();
self.crc32_add(&mut digest);
digest.finalize()
}
/// Create the set of alert_handler register values from a given lifecycle state and OTP.
///
/// The internal fields of `AlertRegs` should match those produced on the device after
/// alert_handler is configured in `sw/device/silicon_creator/lib/shutdown.h:shutdown_init`.
pub fn try_new<T: OtpRead>(lc_state: LcStateVal, otp: &T) -> Result<Self> {
let mut alert = AlertRegs::default();
let lc_shift = match lc_state {
LcStateVal::Prod => 0,
LcStateVal::ProdEnd => 1,
LcStateVal::Dev => 2,
LcStateVal::Rma => 3,
LcStateVal::Test => return Ok(alert),
};
let class_enable = otp.read32("OWNER_SW_CFG_ROM_ALERT_CLASS_EN")?;
let class_escalate = otp.read32("OWNER_SW_CFG_ROM_ALERT_ESCALATION")?;
for i in 0..ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
let value = otp.read32_offset("OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION", i * 4)?;
let cls = AlertClass::try_from(value.to_le_bytes()[lc_shift])?;
let enable = AlertEnable::try_from(class_enable.to_le_bytes()[cls.index()])?;
alert.configure(i, cls, enable)?;
}
for i in 0..ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
let value = otp.read32_offset("OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION", i * 4)?;
let cls = AlertClass::try_from(value.to_le_bytes()[lc_shift])?;
let enable = AlertEnable::try_from(class_enable.to_le_bytes()[cls.index()])?;
alert.local_configure(i, cls, enable)?;
}
for i in 0..ALERT_HANDLER_PARAM_N_CLASSES as usize {
let mut phase_cycs = [0; ALERT_HANDLER_PARAM_N_PHASES as usize];
for phase in 0..ALERT_HANDLER_PARAM_N_PHASES as usize {
phase_cycs[phase] = otp.read32_offset(
"OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES",
(i * phase_cycs.len() + phase) * 4,
)?;
}
let config = AlertClassConfig {
enabled: AlertEnable::try_from(class_enable.to_le_bytes()[i])?,
escalate: AlertEscalate::try_from(class_escalate.to_le_bytes()[i])?,
accum_thresh: otp.read32_offset("OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH", i * 4)?,
timeout_cyc: otp.read32_offset("OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES", i * 4)?,
phase_cycs,
};
alert.class_configure(AlertClass::from_index(i), &config)?;
}
Ok(alert)
}
fn configure(&mut self, index: usize, cls: AlertClass, enabled: AlertEnable) -> Result<()> {
if index >= ALERT_HANDLER_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
bail!("Bad alert index {}", index);
}
self.class[index] = match cls {
AlertClass::A => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSA,
AlertClass::B => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSB,
AlertClass::C => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSC,
AlertClass::D => ALERT_HANDLER_ALERT_CLASS_SHADOWED_0_CLASS_A_0_VALUE_CLASSD,
AlertClass::X => return Ok(()),
};
match enabled {
AlertEnable::None => {}
AlertEnable::Enabled => self.en[index] = 1,
AlertEnable::Locked => {
self.en[index] = 1;
self.regwen[index] = 0;
}
};
Ok(())
}
fn local_configure(
&mut self,
index: usize,
cls: AlertClass,
enabled: AlertEnable,
) -> Result<()> {
if index >= ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_MULTIREG_COUNT as usize {
bail!("Bad local alert index {}", index);
}
self.loc_class[index] = match cls {
AlertClass::A => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSA,
AlertClass::B => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSB,
AlertClass::C => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSC,
AlertClass::D => ALERT_HANDLER_LOC_ALERT_CLASS_SHADOWED_0_CLASS_LA_0_VALUE_CLASSD,
AlertClass::X => return Ok(()),
};
match enabled {
AlertEnable::None => {}
AlertEnable::Enabled => self.loc_en[index] = 1,
AlertEnable::Locked => {
self.loc_en[index] = 1;
self.loc_regwen[index] = 0;
}
};
Ok(())
}
fn class_configure(&mut self, cls: AlertClass, config: &AlertClassConfig) -> Result<()> {
let index = match cls {
AlertClass::A => 0,
AlertClass::B => 1,
AlertClass::C => 2,
AlertClass::D => 3,
AlertClass::X => bail!("Bad class"),
};
let mut reg = 0_u32;
// TODO(lowRISC/opentitan#15443): Fix this lint (clippy::erasing_op):
//reg |= (0 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E0_MASK)
// << ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E0_OFFSET;
reg |= (1 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E1_MASK)
<< ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E1_OFFSET;
reg |= (2 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E2_MASK)
<< ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E2_OFFSET;
reg |= (3 & ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E3_MASK)
<< ALERT_HANDLER_CLASSA_CTRL_SHADOWED_MAP_E3_OFFSET;
let reg_bits = reg.view_bits_mut::<Lsb0>();
match config.enabled {
AlertEnable::None => {}
AlertEnable::Enabled => {
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_BIT as usize, true);
}
AlertEnable::Locked => {
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_LOCK_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_BIT as usize, true)
}
}
match config.escalate {
AlertEscalate::Phase0 => {
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true)
}
AlertEscalate::Phase1 => {
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E1_BIT as usize, true);
}
AlertEscalate::Phase2 => {
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E1_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E2_BIT as usize, true);
}
AlertEscalate::Phase3 => {
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E0_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E1_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E2_BIT as usize, true);
reg_bits.set(ALERT_HANDLER_CLASSA_CTRL_SHADOWED_EN_E3_BIT as usize, true);
}
AlertEscalate::None => {}
}
self.class_regs[index].ctrl = reg;
self.class_regs[index].accum_thresh = config.accum_thresh;
self.class_regs[index].timeout_cyc = config.timeout_cyc;
self.class_regs[index].phase_cycs = config.phase_cycs;
Ok(())
}
}
trait Crc32Add {
fn crc32_add(self, diegst: &mut Digest<u32>);
}
impl Crc32Add for u32 {
fn crc32_add(self, digest: &mut Digest<u32>) {
digest.update(self.to_le_bytes().as_slice())
}
}
impl<T: Crc32Add, const N: usize> Crc32Add for [T; N] {
fn crc32_add(self, digest: &mut Digest<u32>) {
self.map(|v| v.crc32_add(digest));
}
}
impl Crc32Add for AlertClassRegs {
fn crc32_add(self, digest: &mut Digest<u32>) {
self.regwen.crc32_add(digest);
self.ctrl.crc32_add(digest);
self.accum_thresh.crc32_add(digest);
self.timeout_cyc.crc32_add(digest);
self.phase_cycs.crc32_add(digest);
}
}
impl Crc32Add for AlertRegs {
fn crc32_add(self, digest: &mut Digest<u32>) {
self.regwen.crc32_add(digest);
self.en.crc32_add(digest);
self.class.crc32_add(digest);
self.loc_regwen.crc32_add(digest);
self.loc_en.crc32_add(digest);
self.loc_class.crc32_add(digest);
self.class_regs.crc32_add(digest);
}
}
fn new_crc() -> Crc<u32> {
Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
}
#[cfg(test)]
mod test {
use super::*;
// Register values dumped from device after alert_handler initialization.
const TEST_REGS: AlertRegs = AlertRegs {
regwen: [
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
0x00000001, 0x00000001,
],
en: [
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000,
],
class: [
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000,
],
loc_regwen: [
0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001,
],
loc_en: [
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
],
loc_class: [
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
],
class_regs: [
AlertClassRegs {
regwen: 0x00000001,
ctrl: 0x00003900,
accum_thresh: 0x00000000,
timeout_cyc: 0x00000000,
phase_cycs: [0x00000000, 0x0000000a, 0x0000000a, 0xffffffff],
},
AlertClassRegs {
regwen: 0x00000001,
ctrl: 0x00003900,
accum_thresh: 0x00000000,
timeout_cyc: 0x00000000,
phase_cycs: [0x00000000, 0x0000000a, 0x0000000a, 0xffffffff],
},
AlertClassRegs {
regwen: 0x00000001,
ctrl: 0x00003900,
accum_thresh: 0x00000000,
timeout_cyc: 0x00000000,
phase_cycs: [0x00000000, 0x00000000, 0x00000000, 0x00000000],
},
AlertClassRegs {
regwen: 0x00000001,
ctrl: 0x00003900,
accum_thresh: 0x00000000,
timeout_cyc: 0x00000000,
phase_cycs: [0x00000000, 0x00000000, 0x00000000, 0x00000000],
},
],
};
struct TestOtpAlertsDisabled {}
// OTP values that corrispond to the above `TEST_REG` values.
impl OtpRead for TestOtpAlertsDisabled {
fn read32_offset(&self, name: &str, offset: usize) -> Result<u32> {
Ok(match name {
"OWNER_SW_CFG_ROM_ALERT_CLASS_EN" => 0xa9a9a9a9,
"OWNER_SW_CFG_ROM_ALERT_ESCALATION" => 0xd1d1d1d1,
"OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION"
| "OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION" => 0x94949494,
"OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES" => [
0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 0
0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 1
0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 2
0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 3
][offset / 4],
"OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH" | "OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES" => {
0x00000000
}
_ => panic!("No such OTP value {}", name),
})
}
}
struct TestOtpAlertsEnabled {}
// OTP values with `*_CLASS_EN` vales set to `kAlertEnableEnabled`
impl OtpRead for TestOtpAlertsEnabled {
fn read32_offset(&self, name: &str, offset: usize) -> Result<u32> {
Ok(match name {
"OWNER_SW_CFG_ROM_ALERT_CLASS_EN" => 0x07070707,
"OWNER_SW_CFG_ROM_ALERT_ESCALATION" => 0xd1d1d1d1,
"OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION"
| "OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION" => 0x94949494,
"OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES" => [
0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 0
0x00000000, 0x0000000a, 0x0000000a, 0xffffffff, // Class 1
0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 2
0x00000000, 0x00000000, 0x00000000, 0x00000000, // Class 3
][offset / 4],
"OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH" | "OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES" => {
0x00000000
}
_ => panic!("No such OTP value {}", name),
})
}
}
// A sanity test to make sure the correct CRC algorithm is being used.
//
// These values are taken from the CRC32 unit tests in
// `sw/device/silicon_creator/lib/crc32_unittest.cc`.
#[test]
fn test_new_crc() {
let crc = new_crc();
let mut digest = crc.digest();
digest.update(b"123456789");
assert_eq!(digest.finalize(), 0xcbf43926);
let crc = new_crc();
let mut digest = crc.digest();
digest.update(b"The quick brown fox jumps over the lazy dog");
assert_eq!(digest.finalize(), 0x414fa339);
let crc = new_crc();
let mut digest = crc.digest();
digest.update(b"\xfe\xca\xfe\xca\x02\xb0\xad\x1b");
assert_eq!(digest.finalize(), 0x9508ac14);
}
#[test]
fn test_crc_from_regs() {
assert_eq!(TEST_REGS.crc32(), 0xf9616122);
}
#[test]
fn test_regs_from_otp() {
assert_eq!(
TEST_REGS,
AlertRegs::try_new(LcStateVal::Dev, &TestOtpAlertsDisabled {}).unwrap()
);
}
#[test]
fn test_crc_disabled() {
assert_eq!(
AlertRegs::try_new(LcStateVal::Dev, &TestOtpAlertsDisabled {})
.unwrap()
.crc32(),
0xf9616122
);
}
#[test]
fn test_crc_enabled() {
assert_eq!(
AlertRegs::try_new(LcStateVal::Dev, &TestOtpAlertsEnabled {})
.unwrap()
.crc32(),
0x561bcb14
);
}
}