From 3023dfca018c30c87072c4376c6f69dc35aef0d3 Mon Sep 17 00:00:00 2001 From: Matt Derksen Date: Fri, 9 Sep 2016 10:51:57 -0500 Subject: Added CK and DQ vpd accessors Change-Id: Ic72c985b1fe064273bc39f14bcc31595117c6a08 RTC:159347 Reviewed-on: http://ralgit01.raleigh.ibm.com/gerrit1/29421 Tested-by: PPE CI Reviewed-by: Martin Gloff Reviewed-by: Christian R. Geddes Tested-by: Jenkins Server Reviewed-by: Matt K. Light Tested-by: Hostboot CI Reviewed-by: Jennifer A. Stofer Reviewed-on: http://ralgit01.raleigh.ibm.com/gerrit1/29426 Reviewed-by: Hostboot Team Tested-by: Christian R. Geddes --- .../hwp/accessors/p9_get_mem_vpd_keyword.C | 352 +++++++++++++-------- .../hwp/accessors/p9_get_mem_vpd_keyword.H | 21 +- 2 files changed, 237 insertions(+), 136 deletions(-) (limited to 'src/import/chips/p9/procedures/hwp/accessors') diff --git a/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.C b/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.C index 717d3ee5d..9ef8237b3 100644 --- a/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.C +++ b/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.C @@ -71,6 +71,15 @@ struct mappingHeader_t //header is first in mapping keyword uint8_t reserved; // reserved } __attribute__((packed)); +// header for Q0 and CK keywords +struct mappingHeader2_t //header is first in mapping keyword +{ + uint8_t layoutVersion; // decode version + uint8_t numEntries; // number of criteria mapping entries (DQ = # of map entries) + uint8_t blobSize; // # bytes in each data entry + uint8_t reserved; +} __attribute__((packed)); + struct mappingKeywordRow_t //criteria mapping entries follow header { uint8_t mcsMaskMSB; // mcs mask high order byte @@ -81,28 +90,39 @@ struct mappingKeywordRow_t //criteria mapping entries follow header char keywordChar; // 0..9,A..Z } __attribute__((packed)); +struct mappingDqRow_t // DQ map entries following header +{ + fapi2::ATTR_MEMVPD_POS_Type qPosition; + uint8_t qNum; +} __attribute__((packed)); + +const size_t DQ_MAP_SIZE = 36; +const size_t DQ_BLOB_SIZE = 160; + + extern "C" { /// @brief Return VPD keyword based on MCS, VPDInfo, and MR/MT mapping -/// The MR and MT keyword contains a header followed by a table. Each -/// row in the table has criteria to select a vpd keyword. +/// The MR and MT keyword contains a header followed by a table. +/// Each row in the table has criteria to select a vpd keyword. +/// DQ keyword uses Q0 as a map to Q1-Q8 keywords. Q0 and CK have a header. /// /// @param[in] i_target, the MCS /// @param[in] i_vpd_info, vpd criteria -/// @param[in] i_pMapping, MR or MT keyword data +/// @param[in] i_pMapping, MR, MT, Q0, or CK keyword data /// @param[in] i_mappingSize, size of i_pMapping buffer -/// @param[out] o_keywordName, keyword with vpd +/// @param[out] o_keywordInfo, keyword with its vpd information /// @return FAPI2_RC_SUCCESS iff ok fapi2::ReturnCode p9_get_mem_vpd_keyword( const fapi2::Target& i_target, const fapi2::VPDInfo& i_vpd_info, const uint8_t* i_pMapping, const size_t i_mappingSize, - fapi2::keywordName_t& o_keywordName) + fapi2::keywordInfo_t& o_keywordInfo) { char l_first = 0; //first character of return keyword name - char l_second = 0; //second character of return keyword name + char l_second = MAPPING_LAYOUT_INVALID; //second character of return keyword name fapi2::ATTR_MEMVPD_POS_Type l_mcsPos = 0; uint16_t l_mcsMask = 0; uint16_t l_rankMask = 0; @@ -113,49 +133,51 @@ extern "C" uint32_t l_freqTableCnt = sizeof(l_freqTable) / sizeof(l_freqTable[0]); uint32_t l_index = 0; //start with first criteria row uint32_t l_indexMax = MAPPING_LAYOUT_MAXROWS; + o_keywordInfo.kwBlobIndex = 0; // default blob is entire keyword data + o_keywordInfo.kwBlobSize = i_mappingSize; + const mappingHeader_t* l_mappingHeader = //pointer to header reinterpret_cast(i_pMapping); + const mappingHeader2_t* l_mappingHeader2 = //pointer to header2 (used for DQ and CK) + reinterpret_cast(i_pMapping); + const mappingKeywordRow_t* l_mapping = //pointer to first criteria row reinterpret_cast(i_pMapping + sizeof(mappingHeader_t)); FAPI_DBG("p9_get_mem_vpd_keyword: enter"); - // Validate size of mapping to be at least large enough for biggest - // mapping table expected. - const size_t MAPPING_LAYOUT_MAXSIZE = - size_t(sizeof(mappingKeywordRow_t) * MAPPING_LAYOUT_MAXROWS) + - sizeof(mappingHeader_t); - FAPI_ASSERT(MAPPING_LAYOUT_MAXSIZE <= i_mappingSize, - fapi2::GET_MEM_VPD_MAPPING_TOO_SMALL(). - set_SIZE(size_t(i_mappingSize)). - set_EXPECTED(size_t(MAPPING_LAYOUT_MAXSIZE)). - set_TARGET(i_target). - set_VPDTYPE(i_vpd_info.iv_vpd_type), - "Mapping keyword size %d less than min %d expected", - i_mappingSize, - MAPPING_LAYOUT_MAXSIZE); - - // Validate mapping keyword version supported - FAPI_ASSERT(MAPPING_LAYOUT_VERSION == l_mappingHeader->layoutVersion, - fapi2::GET_MEM_VPD_UNSUPPORTED_VERSION(). - set_VERSION(uint8_t(l_mappingHeader->layoutVersion)). - set_EXPECTED(uint8_t(MAPPING_LAYOUT_VERSION)). - set_TARGET(i_target). - set_VPDTYPE(i_vpd_info.iv_vpd_type), - "Header version %d not supported % expected", - l_mappingHeader->layoutVersion, - MAPPING_LAYOUT_VERSION); + size_t l_mapping_layout_maxsize; + uint8_t l_mapping_layout_version; // Validate vpd type and set first keyword name character based on type switch (i_vpd_info.iv_vpd_type) { case fapi2::MT: l_first = 'X'; //MT vpd keyword name X0..X9,XA..XZ + l_mapping_layout_version = MAPPING_LAYOUT_VERSION; + l_mapping_layout_maxsize = sizeof(mappingHeader_t) + + size_t(sizeof(mappingKeywordRow_t) * MAPPING_LAYOUT_MAXROWS); break; case fapi2::MR: l_first = 'J'; //MR vpd keyword name J0..J9,JA..JZ + l_mapping_layout_version = MAPPING_LAYOUT_VERSION; + l_mapping_layout_maxsize = sizeof(mappingHeader_t) + + size_t(sizeof(mappingKeywordRow_t) * MAPPING_LAYOUT_MAXROWS); + break; + + case fapi2::DQ: + l_first = 'Q'; //DQ vpd keyword name Q1...Q8 + l_mapping_layout_version = MAPPING_LAYOUT_VERSION; + l_mapping_layout_maxsize = DQ_MAP_SIZE; + break; + + case fapi2::CK: + l_first = 'C'; //CKE vpd keyword name CK + l_mapping_layout_version = MAPPING_LAYOUT_VERSION; + l_mapping_layout_maxsize = sizeof(mappingHeader2_t) + + (l_mappingHeader2->numEntries * l_mappingHeader2->blobSize); break; default: @@ -165,8 +187,32 @@ extern "C" set_VPDTYPE(i_vpd_info.iv_vpd_type), "Invalid vpd type = %d", i_vpd_info.iv_vpd_type); + break; } + // Validate size of mapping to be at least large enough for biggest + // mapping table expected. + FAPI_ASSERT(l_mapping_layout_maxsize <= i_mappingSize, + fapi2::GET_MEM_VPD_MAPPING_TOO_SMALL(). + set_SIZE(size_t(i_mappingSize)). + set_EXPECTED(l_mapping_layout_maxsize). + set_TARGET(i_target). + set_VPDTYPE(i_vpd_info.iv_vpd_type), + "Mapping keyword size %d less than min %d expected", + i_mappingSize, + l_mapping_layout_maxsize); + + // Validate mapping keyword version supported + FAPI_ASSERT(l_mapping_layout_version == l_mappingHeader->layoutVersion, + fapi2::GET_MEM_VPD_UNSUPPORTED_VERSION(). + set_VERSION(uint8_t(l_mappingHeader->layoutVersion)). + set_EXPECTED(uint8_t(l_mapping_layout_version)). + set_TARGET(i_target). + set_VPDTYPE(i_vpd_info.iv_vpd_type), + "Header version %d not supported % expected", + l_mappingHeader->layoutVersion, + l_mapping_layout_version); + // Get the MCS position and calculate mask FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_MEMVPD_POS, i_target, @@ -176,117 +222,162 @@ extern "C" FAPI_DBG ("p9_get_mem_vpd_keyword: mca position = %d mask=0x%04x", l_mcsPos, l_mcsMask); - // Get the frequency index and calculate mask - FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_MEMVPD_FREQS_MHZ, - fapi2::Target(), - l_freqTable), - "p9_get_mem_vpd_keyword: get ATTR_MEMVPD_FREQS_MHZ failed"); - - for (; l_freqTableIndex < l_freqTableCnt; l_freqTableIndex++) + if (i_vpd_info.iv_vpd_type == fapi2::CK) { - if (i_vpd_info.iv_freq_mhz == l_freqTable[l_freqTableIndex]) - { - break; // found it - } + // verify data index will be valid + FAPI_ASSERT(l_mcsPos < l_mappingHeader2->numEntries, + fapi2::GET_MEM_VPD_ENTRY_NOT_FOUND(). + set_ENTRY(l_mcsPos). + set_MAX_ENTRIES(uint8_t(l_mappingHeader2->numEntries)). + set_VERSION(uint8_t(l_mappingHeader2->layoutVersion)). + set_TARGET(i_target). + set_VPDTYPE(i_vpd_info.iv_vpd_type), + "Unsupported entry (%d), max entries (%d)", + l_mcsPos, + uint8_t(l_mappingHeader2->numEntries)); + + o_keywordInfo.kwBlobSize = l_mappingHeader2->blobSize; + + // Setup index to CK section data (ordered by memvpd pos) + o_keywordInfo.kwBlobIndex = (l_mcsPos * o_keywordInfo.kwBlobSize) + + sizeof(mappingHeader2_t); + + l_second = 'K'; } - - FAPI_ASSERT(l_freqTableIndex < l_freqTableCnt, - fapi2::GET_MEM_VPD_UNSUPPORTED_FREQUENCY(). - set_UNSUPPORTEDFREQ(uint32_t(i_vpd_info.iv_freq_mhz)). - set_MEMVPDFREQ0(uint32_t(l_freqTable[0])). - set_MEMVPDFREQ1(uint32_t(l_freqTable[1])). - set_MEMVPDFREQ2(uint32_t(l_freqTable[2])). - set_MEMVPDFREQ3(uint32_t(l_freqTable[3])). - set_TARGET(i_target). - set_VPDTYPE(i_vpd_info.iv_vpd_type), - "Frequency %d not in ATTR_MEMVPD_FREQS_MHZ", - i_vpd_info.iv_freq_mhz); - l_freqMask = (MAPPING_LAYOUT_FREQ_0 >> l_freqTableIndex); //zero based - FAPI_DBG ("p9_get_mem_vpd_keyword: frequency index = %d mask=0x%02x", - l_freqTableIndex, l_freqMask); - - // Calculate rank count mask. Valid rank counts are 0,1,2 or 4. - // The mask has a bit for each of the 16 rank pairs of - // dimm0 rank count by dimm1 count rank. - // This notation is used by genMemVpd.pl to specify pairs. - // Order= 0x00, 0x01, 0x02, 0x04, 0x11, 0x12, ... 0x42, 0x44 - switch (i_vpd_info.iv_rank_count_dimm_0) + else if (i_vpd_info.iv_vpd_type == fapi2::DQ) { - case 0: //index into pair order by multiplying by 4 - case 1: - case 2: - l_rankShift = i_vpd_info.iv_rank_count_dimm_0 * 4; - break; + // Find which Q# i_target is in + const mappingDqRow_t* l_dqmapping = //pointer to first criteria row + reinterpret_cast(i_pMapping + + sizeof(mappingHeader2_t)); - case 4: // need to use value 3 for rank count 4 - l_rankShift = 3 * 4; - break; + for (uint8_t i = 0; i < l_mappingHeader->numEntries; i++) + { + if (l_dqmapping[i].qPosition == l_mcsPos) + { + l_second = l_dqmapping[i].qNum; + o_keywordInfo.kwBlobSize = DQ_BLOB_SIZE; + break; + } + } - default: - FAPI_ASSERT(false, - fapi2::GET_MEM_VPD_UNSUPPORTED_RANK(). - set_RANK(uint64_t(i_vpd_info.iv_rank_count_dimm_0)). - set_TARGET(i_target). - set_VPDTYPE(i_vpd_info.iv_vpd_type), - "Unsupported rank = %d should be 0,1,2, or 4", - i_vpd_info.iv_rank_count_dimm_0); + FAPI_DBG ("p9_get_mem_vpd_keyword: mcsPos %d -> Q%c keyword data", + l_mcsPos, l_second); } - - switch (i_vpd_info.iv_rank_count_dimm_1) + else { - case 0: //add in dimm 1 rank count index - case 1: - case 2: - l_rankShift += i_vpd_info.iv_rank_count_dimm_1; - break; + // Get the frequency index and calculate mask + FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_MEMVPD_FREQS_MHZ, + fapi2::Target(), + l_freqTable), + "p9_get_mem_vpd_keyword: get ATTR_MEMVPD_FREQS_MHZ failed"); - case 4: // need to use value 3 for rank count 4 - l_rankShift += 3; - break; + for (; l_freqTableIndex < l_freqTableCnt; l_freqTableIndex++) + { + if (i_vpd_info.iv_freq_mhz == l_freqTable[l_freqTableIndex]) + { + break; // found it + } + } - default: - FAPI_ASSERT(false, - fapi2::GET_MEM_VPD_UNSUPPORTED_RANK(). - set_RANK(uint64_t(i_vpd_info.iv_rank_count_dimm_1)). - set_TARGET(i_target). - set_VPDTYPE(i_vpd_info.iv_vpd_type), - "Unsupported rank = %d should be 0,1,2, or 4", - i_vpd_info.iv_rank_count_dimm_1); - } + FAPI_ASSERT(l_freqTableIndex < l_freqTableCnt, + fapi2::GET_MEM_VPD_UNSUPPORTED_FREQUENCY(). + set_UNSUPPORTEDFREQ(uint32_t(i_vpd_info.iv_freq_mhz)). + set_MEMVPDFREQ0(uint32_t(l_freqTable[0])). + set_MEMVPDFREQ1(uint32_t(l_freqTable[1])). + set_MEMVPDFREQ2(uint32_t(l_freqTable[2])). + set_MEMVPDFREQ3(uint32_t(l_freqTable[3])). + set_TARGET(i_target). + set_VPDTYPE(i_vpd_info.iv_vpd_type), + "Frequency %d not in ATTR_MEMVPD_FREQS_MHZ", + i_vpd_info.iv_freq_mhz); + l_freqMask = (MAPPING_LAYOUT_FREQ_0 >> l_freqTableIndex); //zero based + FAPI_DBG ("p9_get_mem_vpd_keyword: frequency index = %d mask=0x%02x", + l_freqTableIndex, l_freqMask); + + // Calculate rank count mask. Valid rank counts are 0,1,2 or 4. + // The mask has a bit for each of the 16 rank pairs of + // dimm0 rank count by dimm1 count rank. + // This notation is used by genMemVpd.pl to specify pairs. + // Order= 0x00, 0x01, 0x02, 0x04, 0x11, 0x12, ... 0x42, 0x44 + switch (i_vpd_info.iv_rank_count_dimm_0) + { + case 0: //index into pair order by multiplying by 4 + case 1: + case 2: + l_rankShift = i_vpd_info.iv_rank_count_dimm_0 * 4; + break; + + case 4: // need to use value 3 for rank count 4 + l_rankShift = 3 * 4; + break; + + default: + FAPI_ASSERT(false, + fapi2::GET_MEM_VPD_UNSUPPORTED_RANK(). + set_RANK(uint64_t(i_vpd_info.iv_rank_count_dimm_0)). + set_TARGET(i_target). + set_VPDTYPE(i_vpd_info.iv_vpd_type), + "Unsupported rank = %d should be 0,1,2, or 4", + i_vpd_info.iv_rank_count_dimm_0); + } - l_rankMask = (MAPPING_LAYOUT_RANKPAIR_00 >> l_rankShift); - FAPI_DBG("p9_get_mem_vpd_keyword: rank0=%d rank1=%d mask=0x%04x", - i_vpd_info.iv_rank_count_dimm_0, - i_vpd_info.iv_rank_count_dimm_1, - l_rankMask); + switch (i_vpd_info.iv_rank_count_dimm_1) + { + case 0: //add in dimm 1 rank count index + case 1: + case 2: + l_rankShift += i_vpd_info.iv_rank_count_dimm_1; + break; + + case 4: // need to use value 3 for rank count 4 + l_rankShift += 3; + break; + + default: + FAPI_ASSERT(false, + fapi2::GET_MEM_VPD_UNSUPPORTED_RANK(). + set_RANK(uint64_t(i_vpd_info.iv_rank_count_dimm_1)). + set_TARGET(i_target). + set_VPDTYPE(i_vpd_info.iv_vpd_type), + "Unsupported rank = %d should be 0,1,2, or 4", + i_vpd_info.iv_rank_count_dimm_1); + } - // Use mapping data to find the second vpd keyword character. - // Select the row where the MCS, Frequency, and rank count pair - // bit are on in the criteria row in the mapping keyword. - if (MAPPING_LAYOUT_MAXROWS > l_mappingHeader->numEntries) - { - l_indexMax = l_mappingHeader->numEntries; - } + l_rankMask = (MAPPING_LAYOUT_RANKPAIR_00 >> l_rankShift); + FAPI_DBG("p9_get_mem_vpd_keyword: rank0=%d rank1=%d mask=0x%04x", + i_vpd_info.iv_rank_count_dimm_0, + i_vpd_info.iv_rank_count_dimm_1, + l_rankMask); - for (l_index = 0; l_index < l_indexMax; l_index++) - { - if (MAPPING_LAYOUT_LAST == l_mapping[l_index].keywordChar) + // Use mapping data to find the second vpd keyword character. + // Select the row where the MCS, Frequency, and rank count pair + // bit are on in the criteria row in the mapping keyword. + if (MAPPING_LAYOUT_MAXROWS > l_mappingHeader->numEntries) { - break; //Hit end of table (not expected, but being careful. - //The keyword is zero padded by genMemVpd.pl + l_indexMax = l_mappingHeader->numEntries; } - if ( (l_mcsMask & - (((l_mapping[l_index].mcsMaskMSB) << 8) | //endian sensitive - l_mapping[l_index].mcsMaskLSB)) && - (l_rankMask & - (((l_mapping[l_index].rankMaskMSB) << 8) | //endian sensitve - l_mapping[l_index].rankMaskLSB)) && - (l_freqMask & l_mapping[l_index].freqMask) ) + for (l_index = 0; l_index < l_indexMax; l_index++) { - // This row covers mca, ranks, and freq - l_second = l_mapping[l_index].keywordChar; - break; + if (MAPPING_LAYOUT_LAST == l_mapping[l_index].keywordChar) + { + break; //Hit end of table (not expected, but being careful. + //The keyword is zero padded by genMemVpd.pl + } + + if ( (l_mcsMask & + (((l_mapping[l_index].mcsMaskMSB) << 8) | //endian sensitive + l_mapping[l_index].mcsMaskLSB)) && + (l_rankMask & + (((l_mapping[l_index].rankMaskMSB) << 8) | //endian sensitive + l_mapping[l_index].rankMaskLSB)) && + (l_freqMask & l_mapping[l_index].freqMask) ) + { + // This row covers mca, ranks, and freq + l_second = l_mapping[l_index].keywordChar; + break; + } } } @@ -301,6 +392,7 @@ extern "C" set_TARGET(i_target). set_VPDTYPE(i_vpd_info.iv_vpd_type), "No match in mapping table"); + //Was a valid keyword name found? FAPI_ASSERT(MAPPING_LAYOUT_INVALID != l_second, fapi2::GET_MEM_VPD_UNSUPPORTED_CONFIGURATION(). @@ -318,11 +410,11 @@ extern "C" "Unsupported configuration"); // build the keyword name - o_keywordName[0] = l_first; - o_keywordName[1] = l_second; - o_keywordName[2] = 0; + o_keywordInfo.kwName[0] = l_first; + o_keywordInfo.kwName[1] = l_second; + o_keywordInfo.kwName[2] = 0; FAPI_DBG("p9_get_mem_vpd_keyword: keyword name = %s", - o_keywordName); + o_keywordInfo.kwName); fapi_try_exit: FAPI_DBG("p9_get_mem_vpd_keyword: exit"); diff --git a/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.H b/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.H index cff749fa2..4baff3fa9 100644 --- a/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.H +++ b/src/import/chips/p9/procedures/hwp/accessors/p9_get_mem_vpd_keyword.H @@ -43,7 +43,7 @@ namespace fapi2 { -/// vpd keyword to be read based on input parameters and MR/MT mapping. +/// vpd keyword to be read based on input parameters and MR/MT/DQ mapping. /// For example, X0...XZ for MT or J0...JZ for MR vpd data. /// Return as a string for convenient debug traces. enum @@ -51,6 +51,14 @@ enum KEYWORD_BYTE_SIZE = 2, }; typedef char keywordName_t [KEYWORD_BYTE_SIZE + 1]; + +typedef struct kwInfo +{ + keywordName_t kwName; // keyword, like X0..XZ, J0..JZ, Q1..Q8, or CK + uint16_t kwBlobSize; // blob size (valid for Q# and CK) + uint16_t kwBlobIndex; // offset of blob from start of keyword data +} keywordInfo_t; + } typedef fapi2::ReturnCode (*p9_get_mem_vpd_keyword_FP_t)( @@ -58,7 +66,7 @@ typedef fapi2::ReturnCode (*p9_get_mem_vpd_keyword_FP_t)( const fapi2::VPDInfo&, const uint8_t*, const size_t, - fapi2::keywordName_t&); + fapi2::keywordInfo_t&); extern "C" { @@ -88,7 +96,7 @@ extern "C" /// 0x80 index 0 /// ... through .. /// 0x10 index 3 - // (frequency index 4-7 reserved) + /// (frequency index 4-7 reserved) /// dddd second char in keyword name /// /// All the configurations for a particular keyword are 'OR'ed into the @@ -98,19 +106,20 @@ extern "C" /// /// Note: this interface should not be called directly by HWPs, /// it is provided for platform use behind the getVPD() interface. + /// DQ keyword uses Q0 as a map to Q1-Q8 keywords. Q0 and CK have a header. /// /// @param[in] i_target, the MCS /// @param[in] i_vpd_info, vpd criteria - /// @param[in] i_pMapping, MR or MT keyword data + /// @param[in] i_pMapping, MR, MT, Q0, or CK keyword data /// @param[in] i_mappingSize, size of i_pMapping buffer - /// @param[out] o_keywordName, keyword with vpd + /// @param[out] o_keywordInfo, keyword with its vpd information /// @return FAPI2_RC_SUCCESS iff ok fapi2::ReturnCode p9_get_mem_vpd_keyword ( const fapi2::Target& i_target, const fapi2::VPDInfo& i_vpd_info, const uint8_t* i_pMapping, const size_t i_mappingSize, - fapi2::keywordName_t& o_keywordName); + fapi2::keywordInfo_t& o_keywordInfo); } -- cgit v1.2.1