From 916c31cdf8028b4a5f9ddf95b766a4b83353c175 Mon Sep 17 00:00:00 2001 From: whs Date: Wed, 25 May 2016 09:26:29 -0500 Subject: Create tool to generate memory vpd keyword mappings Process input directory of vpd text files and create binary keyword files and a mapping binary keyword file. Change-Id: I8c1a5e03154b4d516ac20d10b222a84e17b244fe RTC: 154580 Reviewed-on: http://ralgit01.raleigh.ibm.com/gerrit1/25009 Tested-by: Jenkins Server Reviewed-by: Brian R. Silver Reviewed-by: Daniel M. Crowell Reviewed-by: Jennifer A. Stofer Reviewed-on: http://rchgit01.rchland.ibm.com/gerrit1/79897 Tested-by: Jenkins Server Tested-by: Jenkins OP Build CI Tested-by: FSP CI Jenkins Tested-by: Jenkins OP HW --- src/import/tools/genMemVpd.pl | 1291 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1291 insertions(+) create mode 100755 src/import/tools/genMemVpd.pl (limited to 'src/import/tools') diff --git a/src/import/tools/genMemVpd.pl b/src/import/tools/genMemVpd.pl new file mode 100755 index 000000000..172f72e8a --- /dev/null +++ b/src/import/tools/genMemVpd.pl @@ -0,0 +1,1291 @@ +#!/usr/bin/perl +# IBM_PROLOG_BEGIN_TAG +# This is an automatically generated prolog. +# +# $Source: src/import/tools/genMemVpd.pl $ +# +# OpenPOWER HostBoot Project +# +# Contributors Listed Below - COPYRIGHT 2016,2019 +# [+] International Business Machines Corp. +# +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. +# +# IBM_PROLOG_END_TAG + +################################################################################ +# This script creates binary vpd files to be used to build the direct memory +# eeprom image. There are two 'types', MR and MT. MR is for the 'memory rotator' +# direct memory attributes and MT is for the 'memory terminator' direct memory +# attributes. +# +# For MR and MT, there can be unique attribute values based on the +# MCS position, the data rate of the system, and the rank count of the dimms +# plugged in. +# +# There are 16 possible MCS positions (4 possible procs x 4 MCS +# positions). MRW attribute ATTR_MEMVPD_POS, similiar to ATTR_FAPI_POS, +# but will differ on multiple node systems, goes from 0 to 15. For a +# 2 proc system, there will only be 8 MCSs, but up to 16 is supported by the +# script for the general case. +# +# It was 'decided' to support up to 4 data rates per system. The four values +# are specified in MRW ATTR_MEMVPD_FREQS_MHZ. The four values are passed to +# this script since the script has no connection to the MRW. +# +# Each dimm can have rank count 0 (not there), 1, 2, or 4 ranks. Each +# 'pair' combination (dimm0 rank count by dimm1 rank count), may need +# unique vpd attribute values. There are 16 combinations of dimm0 rank count by +# dimm1 rank count. For MR, the rank count is summarized into drop 1 for +# those combinations with only one dimm (a 0 in the pair) and drop 2 for +# those combinations with two dimms (does not have a 0 in the pair). +# +# There are 1024 (16 MCS positions x 4 freqencies x 16 dimm rank pairs) +# configurations. In a particular system, some may not be supported and +# many commbinations will use the same attribute values. It was 'decided' +# to support up to 36 unique sets of values. For MR, they are stored in +# vpd keywords J0..J9,JA..JZ and for MT in vpd keywords X0..X9,XA..XZ. +# In practice, the eeprom will likely not be big enough for this many +# keywords (each is 255 bytes), so the actual number will be less. +# +# The 'values' for the attributes in a vpd keyword are provided in files +# with a '.vpd' extension and is in the format of an attribute override file, +# but only has direct values (no enumerations) so that this script does not +# have to be built to a particular build, like the attribute override tool. +# +# The vpd text file (.vpd extension) also has a header to specify the data +# rate frequencies and rank count pairs supported by the file. The 'target' +# line specifies the MCS positions supported. There can be multiple target +# lines, similiar to an attribute override file, but only one header which +# must preceed the first target line. +# +# example partial file: template_mr_00_freq_2400_2133_drop_2_1.vpd +# +# #DATA_RATE = 2400, 2133 +# #NUM_DROPS = 2, 1 +# target = k0:n0:s0:p9.mcs:p0,1:call +# #MSS:description:MR Keyword Layout Version Number. Increases when attributes +# # are added, removed, or redefined. Does not reset. +# #MSS:mssUnit:num +# ATTR_MSS_VPD_MR_0_VERSION_LAYOUT u8 0x00 +# #MSS:description:MR Keyword Data Version Number. Increases when data changes +# # with the above layout version. Resets when layout version number +# # increments. +# #MSS:mssUnit:num +# ATTR_MSS_VPD_MR_1_VERSION_DATA u8 0x00 +# #MSS:description:Hash Signature for the MT Keyword. The hash signature is +# # 32bits for 256 bytes of data. +# #MSS:mssUnit:hash +# ATTR_MSS_VPD_MR_2_SIGNATURE_HASH u32 0x00000000 +# #MSS:description:Phase rotator delay value of command/address of A## in +# # ticks. Ticks are 1/128 of one cycle of clock. +# #MSS:mssUnit:tick +# ATTR_MSS_VPD_MR_MC_PHASE_ROT_ADDR_A00[0] u8[2] 0x03 +# ATTR_MSS_VPD_MR_MC_PHASE_ROT_ADDR_A00[1] u8[2] 0x08 +# +# example partial file: template_mt_00_dimm0dimm1_10_20_11_22.vpd +# +# #RANK_CONFIG = 0x10, 0x20, 0x11, 0x22 +# target = k0:n0:s0:p9.mcs:p0,1:call +# #MSS:description:MT Keyword Layout Version Number. Increases when attributes +# # are added, removed, or redefined. Does not reset. +# #MSS:mssUnit:num +# ATTR_MSS_VPD_MT_0_VERSION_LAYOUT u8 0x00 +# #MSS:description:MT Keyword Data Version Number. Increases when data changes +# # with the above layout version. Resets when layout version number increments +# #MSS:mssUnit:num +# ATTR_MSS_VPD_MT_1_VERSION_DATA u8 0x00 +# #MSS:description:Hash Signature for the MT Keyword. The hash signature is +# # 32bits for 256 bytes of data. +# #MSS:mssUnit:encode +# ATTR_MSS_VPD_MT_2_SIGNATURE_HASH u32 0x00000000 +# #MSS:description:Memory Controller side Read Vref setting. Dividing by 1000 +# # gives you percentage of Vdd +# #MSS:enum:VDD40375 = 40375, ... +# #MSS:mssUnit:percent of Vdd +# ATTR_MSS_VPD_MT_VREF_MC_RD[0] u32[2] 0x000131AA +# ATTR_MSS_VPD_MT_VREF_MC_RD[1] u32[2] 0x000131AA +# +# The script parses the ATTR_ lines for values to build the binary file, +# but does not care about the specifics, other then data type (u8, u16,..) +# and value. +# +# The script also builds a "mapping" from the configurations in the vpd +# text files to the generated keyword files. The mapping is provided in +# a binary MR and MT keyword file. At run time, the getVPD call is passed +# a VPDInfo structure with frequency and dimm counts. The target's MEMVPD_POS, +# frequency, and dimm rank count pair is used with the MR or MT keyword +# mapping to select a vpd keyword (X0..XZ or J0..JZ), returning the values. +# +# More details... +# +# The order of the frequencies passed to the script and the order in the MRW +# is significant as the "index" into the list is encoded into the mapping +# which will be matched to the "index" of the VPDInfo frequency to the MRW +# list. The frequency list is a required parameter. +# +# The format of the file name of the vpd text files is: +# $SystemName_$KeyWordType_$DecoderVersion_$KeyWordDependentInfo.vpd +# $KeyWordType is expected to be MR or MT (case insensitive) +# The script picks the files with the highest $DecoderVersion and +# replaces the output files. +# $SystemName_$KeyWordType is a required parameter, called the 'prefix'. +# +# The input and output directories can be optionally specified as parameters. +# The same directory can be used for both. The default is to use the +# directory where the script is (./). +# +# The script also creates 3 information files. +# $SystemName_$KeyWordType_mapping.csv has a text version of the mapping +# keyword for human readable convenience. +# $SystemName_$KeyWordType_trace.txt has a detailed execution trace for +# debuging. +# $SystemName_$KeyWordType_report.csv shows the mapped to keyword +# for each of the 1024 possible configurations along with the vpd text +# file it came from. There should not be any "duplicates", meaning there +# was a overlap in the vpd text file specifications. Either the vpd text +# files need to be fixed, or there is a bug in the script. The report +# can also be used to debug if at run time the wrong vpd is thought to have +# been returned. + +#TODO RTC:154580 support split eeproms + +use strict; + +################################################################################ +# Use of the following packages +################################################################################ + +use Getopt::Long; + +################################################################################ +# define constants and data type conventions +################################################################################ + +# a number is a hash with these members +use constant NUM_VALUE => "num_value"; #numeric value +use constant NUM_SIZE => "num_size"; #number of bytes + +# a fileInfo is a hash with these members +use constant FILE_NAME => "file_name"; #name of the binary file to write +use constant FILE_SIZE => "file_size"; #final size to create +use constant FILE_DATA => "file_data"; #array to hold data until written +use constant FILE_PTR => "file_ptr" ; #current end of data pointer + +# a configuration is a hash with these members +use constant CONF_MCS => "config_mcs_mask"; +use constant CONF_FREQ => "config_freq_mask"; +use constant CONF_RANK => "config_rank_mask"; +use constant CONF_KEY_CHAR => "config_keyword_character"; +use constant CONF_VPD_TEXT_FILE => "config_vpd_text_file"; +use constant CONF_BIN_FILE => "config_bin_file"; + +# global constants +use constant VPD_BIN_FILE_SIZE => 255 ; #all vpd binary key word file size + +################################################################################ +# configuration structures. +# These masks (and the version) are written to the binary mapping vpd keyword +# and used by the decode hwp. +################################################################################ +use constant MAPPING_LAYOUT_VERSION => 1; #version of decode algorithm + +my %g_freqMask = ( #MRW frequency index to mask + 0 => 0x80, #first listed MRW freq... + 1 => 0x40, + 2 => 0x20, + 3 => 0x10); #last listed MRW freq +use constant FREQ_ALL => 0xf0; + +my %g_rankMask = ( #dimm rank count pair to mask + 0x00 => 0x8000, + 0x01 => 0x4000, #dimm0 rank count=0 dimm1 rank count=1 + 0x02 => 0x2000, + 0x04 => 0x1000, + 0x10 => 0x0800, + 0x11 => 0x0400, + 0x12 => 0x0200, #dimm0 rank count=1 dimm1 rank count=2 + 0x14 => 0x0100, + 0x20 => 0x0080, + 0x21 => 0x0040, + 0x22 => 0x0020, + 0x24 => 0x0010, + 0x40 => 0x0008, + 0x41 => 0x0004, + 0x42 => 0x0002, #dimm0 rank count=4 dimm1 rank count=2 + 0x44 => 0x0001); +use constant RANK_DROP1 => 0x7888; #pairs with just one dimm +use constant RANK_DROP2 => 0x0777; #pairs with two dimms + +my %g_mcsMask = ( #MCS MEMVPD_POS to mask + 0 => 0x8000, #proc=0 mcs position=0 memvpd_pos=0 + 1 => 0x4000, + 2 => 0x2000, + 3 => 0x1000, + 4 => 0x0800, #proc=1 mcs position=0 memvpd_pos=4 + 5 => 0x0400, + 6 => 0x0200, + 7 => 0x0100, + 8 => 0x0080, #proc=2 mcs position=0 memvpd_pos=8 + 9 => 0x0040, + 10 => 0x0020, + 11 => 0x0010, + 12 => 0x0008, #proc=3 mcs position=0 memvpd_pos=12 + 13 => 0x0004, + 14 => 0x0002, + 15 => 0x0001); +use constant MCS_ALL => 0xffff; +use constant MAX_NUM_PROCS => 4; +use constant MAX_POSITION => 3; + +################################################################################ +# Global data +################################################################################ +my @g_MrwFreq = (); #MRW MEMVPD_FREQS_MHZ input parameter +my %g_configs = (); #hash of configuration hashes (a list of all configs) +my $g_tarType = ""; #supported target types (MR or MT) + +################################################################################ +# Main flow: +# - get parameters and validate +# - get list of vpd text files to process +# - process each vpd text file +# - construct mapping and keyword binary file +# - construct report +################################################################################ + +sub main{ } +my $cfgMrwMemVpdFreqsMhz = undef; +my $cfgPrefix = undef; +my $cfgInputVpdTextDir = "."; +my $cfgOutputVpdBinDir = "."; +my $cfgHelp = 0; +my $cfgVerbose = 0; + +# Process command line parameters, issue help text if needed +GetOptions("mrw-MEMVPD-FREQS-MHZ:s" => \$cfgMrwMemVpdFreqsMhz, + "prefix:s" => \$cfgPrefix, + "input-vpd-text-dir:s" => \$cfgInputVpdTextDir, + "output-vpd-bin-dir:s" => \$cfgOutputVpdBinDir, + "help" => \$cfgHelp, + "verbose" => \$cfgVerbose ); + +if ($cfgHelp) +{ + display_help(); + exit(0); +} + +# Check mandatory parameters +if ($cfgMrwMemVpdFreqsMhz eq undef) +{ + print STDERR "\n==>mrw-MEMVPD-FREQS-MHZ is a required parameter\n"; +} +if ($cfgPrefix eq undef) +{ + print STDERR "\n==>prefix is a required parameter\n"; +} + +if ($cfgMrwMemVpdFreqsMhz eq undef || + $cfgPrefix eq undef) +{ + display_help(); + exit(1); +} + +# Validate vpd type +{ + (my $system,$g_tarType) = split(/\_/,$cfgPrefix); + $g_tarType = uc $g_tarType; + if ( ("MR" ne $g_tarType) && + ("MT" ne $g_tarType)) + { + fatal("error in --prefix parameter: $cfgPrefix". + " unsupported target type = $g_tarType"); + } +} + +# Parse parameter MRW frequencys +{ + my ($freq0,$freq1,$freq2,$freq3) = split(',',$cfgMrwMemVpdFreqsMhz); + @g_MrwFreq = (str2value($freq0), + str2value($freq1), + str2value($freq2), + str2value($freq3)); +} + +# Ensure directories consistently end with a / +{ + local $/ = '/'; #use temporary version of $/ for the chomp + chomp($cfgInputVpdTextDir); + $cfgInputVpdTextDir .= "/"; + + chomp($cfgOutputVpdBinDir); + $cfgOutputVpdBinDir .= "/"; +} + +# Show parameters +{ + verbose("Parameters="); + verbose(" MRW MEMVPD_FREQS_MHZ =" + ." $g_MrwFreq[0],$g_MrwFreq[1],$g_MrwFreq[2],$g_MrwFreq[3]"); + verbose(" Prefix = $cfgPrefix"); + verbose(" Input vpd text file directory = $cfgInputVpdTextDir"); + verbose(" Ouput vpd bin file directory = $cfgOutputVpdBinDir"); +} + +# Get the list of input vpd text files from the input directory +my @vpdTextFiles = getVpdTextFileList($cfgInputVpdTextDir,$cfgPrefix); +{ + my $numVpdTextFiles = scalar(@vpdTextFiles); + if ($numVpdTextFiles == 0) + { + fatal("No input vpd text files"); + } + verbose("Input Vpd Text Files ($numVpdTextFiles)="); + foreach my $file(@vpdTextFiles) + { + verbose(" $file"); + } +} + +# Process vpd text files +foreach my $file(@vpdTextFiles) +{ + processVpdTextFile($file); +} + +# Create VPD mapping binary file +createMappingFile(); + +# Create report +createReport(); + +# Show what files have been created +{ + my @keys = sort {$a <=> $b} keys %g_configs; + my $numBinVpdFiles = (scalar @keys) + 1; + verbose("Output binary Vpd Files ($numBinVpdFiles)="); + verbose(" $cfgPrefix"."_".$g_tarType.".bin"); + foreach my $key (@keys) + { + my $ref_config = $g_configs{$key}; + verbose(" $ref_config->{CONF_BIN_FILE}"); + } + verbose("Support files created(3)="); + verbose(" $cfgPrefix"."_map.csv"); + verbose(" $cfgPrefix"."_report.csv"); + verbose(" $cfgPrefix"."_trace.csv"); +} + +verbose("Sucessful completion"); +exit(0); + +################################################################################ +# Get the list of input vpd text files from input directory +# +# file name format = +# $SystemName_$KeyWordType_$DecoderVersion_$KeyWordDependentInfo.vpd +# Return the files of the highest $DecoderVersion. This is the mem +# team decode version of the vpd data, not the decode of the selection +# criteria done by p9_get_mem_vpd_keyword.C +################################################################################ +sub getVpdTextFileList +{ + my($inputVpdTextDir,$prefix) = @_; + + opendir(FILEDIR,$inputVpdTextDir) || + fatal("Couldn't open input vpd text file dir $inputVpdTextDir: $!"); + # get ALL the vpd files to find the largest vpd decode version + my @allVpdTextFiles = grep { /^$prefix.*vpd/ } readdir(FILEDIR); + my $largestDecode = ""; + my $largestDecodeValue = -1; + foreach my $vpdFile (@allVpdTextFiles) + { + my($system,$tarType,$decode,$rest) = split(/\_/,$vpdFile); + my $decodeValue = eval($decode); + if (undef eq $decodeValue) + { + warning("decode $decode is not a number: $vpdFile SKIPPED"); + } + if ($decodeValue > $largestDecodeValue) + { + $largestDecodeValue=$decodeValue; + $largestDecode =$decode; + } + } + + # only get files for the largest attribute decode version + rewinddir(FILEDIR); + my $prefixDecode = $prefix."_".$largestDecode; + my @vpdTextFiles = grep { /^$prefixDecode.*vpd/ } + readdir(FILEDIR); + + closedir FILEDIR; + + return @vpdTextFiles +} + +################################################################################ +# Process vpd text file +# +# Parsing logic: (example of vpd text file in initial prologue) +# - keep reading lines as long as $stateKeepReading +# - process the header, which has the frequrency and rank pair/drop +# configuration lines +# - the first target line marks the end of the header. +# - there can be multiple target lines +################################################################################ + +sub processVpdTextFile +{ + my($vpdFile) = @_; + + trace("Process file = ".$vpdFile); + + my %config = (); # configuration for current section + my %fileInfo = (); # fileInfo for current section + my $freqMask = 0; # frequenices (data rates) for this vpd file + my $rankMask = 0; # dimm rank count pairs for this vpd file + my $mcsMask = 0; # MCS mask for this target + my $row = 0; # row count for error message + + # Open vpd text file to parse + open(VPDINPUTTEXT,"<$cfgInputVpdTextDir$vpdFile") + or fatal("open failed for $cfgInputVpdTextDir$vpdFile: $!"); + + # State variables to control parsing the vpd text file + my $stateKeepReading = 1; #keep reading lines until EOF + my $stateProcHeader = 1; #process header lines until first target line + my $stateProcSection = 0; #working on the attributes for a target line + my $stateDataRateSet = 0; #Data Rate (frequency) as been found or default + my $stateRankConfigSet = 0; #Rank Count (drop) has been found + + while ($stateKeepReading) + { + + # Actions to be be performed based on this line of text + my $actionDataRate=0; + my $actionRankConfig=0; + my $actionDropConfig=0; + my $actionTarget=0; + my $actionAttr=0; + my $actionWriteFile=0; + + my $line = ; + $row++; + if ($line) #determine all actions based on text line + { + chomp($line); + if ($line =~ /ATTR_/) #first since will be the most of these + { + if ($stateProcHeader) + { + fatal("ATTR before header complete:\n". + " $vpdFile\n". + " row $row:$line"); + } + $actionAttr=1; + } + elsif ($line =~ /DATA_RATE/) + { + if (!$stateProcHeader) + { + fatal("DATA_RATE outside of header:\n". + " $vpdFile\n". + " row $row:$line"); + } + $actionDataRate=1; + } + elsif ($line =~ /RANK_CONFIG/) + { + if (!$stateProcHeader) + { + fatal("RANK_CONFIG outside of header:\n". + " $vpdFile\n". + " row $row:$line"); + } + $actionRankConfig=1; + } + elsif ($line =~ /NUM_DROPS/) + { + if (!$stateProcHeader) + { + fatal("NUM_DROPS outside of header:\n". + " $vpdFile\n". + " row $row:$line"); + } + $actionDropConfig=1; + } + elsif ($line =~ /target/) + { + # MT files can default to all frequencies if not specificed + if ((!$stateDataRateSet) && + ("MT" eq $g_tarType)) + { + $freqMask = FREQ_ALL; + trace ("Freq Mask = 0x".sprintf("%02X",$freqMask). + " - DEFAULT VALUE"); + $stateDataRateSet = 1; + } + if ($stateDataRateSet and $stateRankConfigSet) + { + $stateProcHeader = 0; + $actionTarget=1; + } + else + { + fatal("target before header is complete:\n". + " $vpdFile\n". + " row $row:$line"); + } + if ($stateProcSection) + { + $actionWriteFile=1; #process previous section before next + } + } + # At this point, everthing of interest has been processed. + # The only lines not processed above should be comment lines, + # which can be ignored (skipped). + } + else # determine actions based on hitting end of the text file + { + $stateKeepReading=0; + if ($stateProcSection) + { + $actionWriteFile=1; + } + } + + # Process all actions needed based on text line + if ($actionDataRate) + { + $freqMask = procDataRate($line,$vpdFile,$row); + $stateDataRateSet = 1; + } + if ($actionRankConfig) + { + $rankMask = procRankConfig($line,$vpdFile,$row); + $stateRankConfigSet = 1; + } + if ($actionDropConfig) + { + $rankMask = procDropConfig($line,$vpdFile,$row); + $stateRankConfigSet = 1; + } + # needs to be before target in case there is a previous target to + # finish out before starting the next section. + if ($actionWriteFile) + { + addConfiguration(\%config); #capture final configuration + fileWrite(\%fileInfo); + } + if ($actionTarget) + { + $mcsMask = procTarget($line,$vpdFile,$row); + $stateProcSection = 1; + %config = (); + %config = newConfiguration($mcsMask, + $freqMask, + $rankMask, + $vpdFile); + %fileInfo = (); + %fileInfo = newFileInfo($config{CONF_BIN_FILE}, + VPD_BIN_FILE_SIZE ); + + traceConfig("createConfig:",\%config); + } + if ($actionAttr) + { + my($attr,$type,$value) = split(/\s+/,$line); + my %num = newNum($value,$type); + trace(" $attr $type $value bytes=$num{NUM_SIZE}"); + filePushNum(\%fileInfo,\%num); + } + } +} + +# process the DATA_RATE line +# +# Must be in the 'header' before the first 'target' line +# # DATA_RATE = XXXX[,XXXX,XXXX,XXXX] +# at least one frequency, up to 4 frequencies +sub procDataRate +{ + my($line,$file,$row) = @_; + + my $freqMask = 0; + + (undef,my $rest) = split(/\=/,$line); + + my @freqs = split(/\,/,$rest); + foreach my $freqText (@freqs) + { + my $freq =eval($freqText); + if (undef eq $freq) + { + fatal("Invalid freq value = $freqText\n". + " $file\n". + " row $row: $line"); + } + + my $found=0; + for (my $i=0 ; $i < scalar @g_MrwFreq ; $i++) + { + if ($freq == $g_MrwFreq[$i]) + { + $freqMask |= $g_freqMask{$i}; + $found=1; + last; + } + } + if (!$found) + { + fatal("$freq not in MRW freq list\n". + " $file\n". + " row $row: $line"); + } + } + trace ("Freq Mask = 0x".sprintf("%02X",$freqMask)); + return $freqMask; +} + +# process the RANK_CONFIG line +# +# Must be in the 'header' before the first 'target' line +# # RANK_CONFIG = 0xXX[,0xXX,...,0xXX] +# X = 0,1,2, or 4 +# at least one dimm count pair, up to 16 pairs +# the 0xXX format is for reability, its just a number (0x10 same as 16). +# The first X is dimm0 rank count. Second is dimm1 rank count. +sub procRankConfig +{ + my($line,$file,$row) = @_; + + my $rankMask = 0; + + (undef,my $rest) = split(/\=/,$line); + + my @ranks = split(/\,/,$rest); + foreach my $rankText (@ranks) + { + my $rank =eval($rankText); + if (undef eq $rank) + { + fatal("Invalid rank value = $rankText\n". + " $file\n". + " row $row: $line"); + } + my $mask = $g_rankMask{$rank}; + if (undef == $mask) + { + fatal("Unsupported rank configuration = 0x". + sprintf("%02X",$rank)."\n". + " $file\n". + " row $row: $line"); + } + $rankMask |= $mask; + } + trace ("Rank Config = 0x".sprintf("%04X",$rankMask)); + return $rankMask; +} + +# process the NUM_DROPS line +# +# Must be in the 'header' before the first 'target' line +# # NUM_DROPS = X[,X] +# X = 0 or 1 +sub procDropConfig +{ + my($line,$file,$row) = @_; + + my $dropMask = 0; + + (undef,my $rest) = split(/\=/,$line); + + my @drops = split(/\,/,$rest); + foreach my $dropText (@drops) + { + my $drop =eval($dropText); + if (undef eq $drop) + { + fatal("Invalid drop value = $dropText\n". + " $file\n". + " row $row: $line"); + } + if (1 == $drop) + { + $dropMask |= RANK_DROP1; + } + elsif (2 == $drop) + { + $dropMask |= RANK_DROP2; + } + else + { + fatal("Unsupported drop configuration = $drop\n". + " $file\n". + " row $row: $line"); + } + } + trace ("Num Drops Config = 0x".sprintf("%04X",$dropMask)); + return $dropMask; +} + +# process the target line +# +# Concludes the 'header'. +# target = k0:n0:s0:p9.mcs:pXXX:cXXX +# XXX = all or a list of comma separated positions (0,1,2 or 3). +sub procTarget +{ + my($line,$file,$row) = @_; + + my $mcsMask = 0; + + (undef,my $rest) = split(/\=/,$line); + (undef,undef,undef,my $p9mcs,my $proc,my $chip) = split(/\:/,$line); + + if ( ("p9.mcs" ne $p9mcs) && + ("pu.mcs" ne $p9mcs) && + ("p9n.mcs" ne $p9mcs) ) + { + fatal("Invalid target value = $p9mcs, ". + "p9.mcs, pu.mcs or p9n.mcs expected\n". + " $file\n". + " row $row: $line"); + } + + my @plist = procPositions($line,$file,$row,$proc,"p"); + my @clist = procPositions($line,$file,$row,$chip,"c"); + + #The MCS bit mask (g_mscMask) is based on the ATTR_MEMVPD_POS of the MCS. + #The MEMVPD_POS is the hash key to find the mask value in g_mscMask. + #Proc 0 MCS 0 is bit 0x8000. MEMVPD_POS = 0 + #Proc 0 MCS 1 is bit 0x4000. MEMVPD_POS = 1, ... etc. + #MEMVPD_POS can be calculated by proc num * 4 + mcs position within the proc + foreach my $ppos(@plist) + { + foreach my $cpos(@clist) + { + my $memVpdPos = ($ppos * 4) + $cpos; + $mcsMask |= $g_mcsMask{$memVpdPos}; + } + } + + trace("Target Mask = 0x".sprintf("%04X",$mcsMask)); + return $mcsMask; +} + +# helper to process positions +# pXXX or cXXX +# where XXX is "all" or a comma separated list of digits 0,1,2,3 +sub procPositions +{ + my($line,$file,$row,$position,$expected) = @_; + + my @list = (); + + my $type = substr($position,0,1); + my $rest = substr($position,1); + if ($type ne $expected) + { + fatal("Invalid target specification $type, $expected expected\n". + " $file\n". + " row $row: $line"); + } + + if ("ALL" eq uc($rest)) + { + for (my $i=0; $i< MAX_NUM_PROCS ; $i++) + { + $list[$i]=$i; + } + } + else + { + my $i = 0; + my @positions = split(/\,/,$rest); + foreach my $posText (@positions) + { + my $pos =eval($posText); + if (undef eq $pos) + { + fatal("Invalid position value = $posText\n". + " $file\n". + " row $row: $line"); + } + if (MAX_POSITION < $pos) + { + fatal("Position out of 0 to ".MAX_POSITION. + " range = $posText\n". + " $file\n". + " row $row: $line"); + } + $list[$i++]=$pos; + } + } + return @list; +} + +################################################################################ +# File functions +################################################################################ + +# create a new file info +sub newFileInfo +{ + my($fileName,$fileSize) = @_; + + my %fileInfo = (); + $fileInfo{FILE_NAME}=$fileName; + $fileInfo{FILE_SIZE}=$fileSize; + $fileInfo{FILE_PTR}=0; + + return %fileInfo; +} + +# append "number" to the end of data to be written +sub filePushNum +{ + my $ref_fileInfo = shift; + my $ref_num = shift; + + my $newPtr = $ref_fileInfo->{FILE_PTR} + $ref_num->{NUM_SIZE}; + if($newPtr > $ref_fileInfo->{FILE_SIZE}) + { + fatal("bin file size exceeded\n". + " file=$ref_fileInfo->{FILE_NAME}\n". + " limit=$ref_fileInfo->{FILE_SIZE}"); + } + $ref_fileInfo->{FILE_PTR}=$newPtr; + + my $fullValue = $ref_num->{NUM_VALUE}; + my $byteValue = $fullValue % 256; + for (my $i=0 ; $i < $ref_num->{NUM_SIZE} ; $i++) + { + $newPtr--; + $ref_fileInfo->{FILE_DATA}[$newPtr] = $byteValue; #big endian order + $fullValue -= $byteValue; + $fullValue /= 256; + $byteValue = $fullValue % 256; + } +} + +# Write the data to the file +sub fileWrite +{ + my $ref_fileInfo = shift; + + my $outBinFile= $cfgOutputVpdBinDir.$ref_fileInfo->{FILE_NAME}; + open(BIN_FILE,">:raw",$outBinFile) || + fatal("couldn't open $outBinFile: $!"); + + for (my $i=0 ; $i<$ref_fileInfo->{FILE_PTR}; $i++) + { + print BIN_FILE pack('C',$ref_fileInfo->{FILE_DATA}[$i]); + } + for (my $i=$ref_fileInfo->{FILE_PTR}; $i<$ref_fileInfo->{FILE_SIZE}; $i++) + { + print BIN_FILE pack('C',0) + } + close(BIN_FILE); + my $pad = $ref_fileInfo->{FILE_SIZE} - $ref_fileInfo->{FILE_PTR}; + + trace("create bin file = $ref_fileInfo->{FILE_NAME} ". + "data=$ref_fileInfo->{FILE_PTR} pad = $pad"); +} + +################################################################################ +# Configuration helpers +################################################################################ + +# create a configuration +my $static_keyChar = undef; +sub newConfiguration +{ + my($mcsMask,$freqMask,$rankMask,$vpdFile) = @_; + + my %config= (); + $config{CONF_MCS}=$mcsMask; + $config{CONF_FREQ}=$freqMask; + $config{CONF_RANK}=$rankMask; + $config{CONF_VPD_TEXT_FILE}=$vpdFile; + + if (undef eq $static_keyChar) + { + $static_keyChar = '0'; + } + elsif ('9' eq $static_keyChar) + { + $static_keyChar = 'A'; + } + elsif ('Z' eq $static_keyChar) + { + fatal("Ran out of keyword characters"); + } + else + { + $static_keyChar++; + } + $config{CONF_KEY_CHAR}=ord($static_keyChar); + + my $binFileName = ""; + if ("MR" eq uc $g_tarType) + { + $binFileName = $cfgPrefix.'_J'.$static_keyChar.".bin"; + } + elsif ("MT" eq uc $g_tarType) + { + $binFileName = $cfgPrefix.'_X'.$static_keyChar.".bin"; + } + else + { + fatal("unsupported target type = $g_tarType"); + } + $config{CONF_BIN_FILE}=$binFileName; + + return %config; +} + +# capture a copy of a configuration and add to global list +sub addConfiguration +{ + my $ref_config = shift; + + my %config = %{$ref_config}; #creat a copy + + $g_configs{$config{CONF_KEY_CHAR}}=\%config; +} + +# create mapping bin file and csv +sub createMappingFile +{ + # open file for human readable csv version of mapping data + (my $system,my $tarType) = split(/\_/,$cfgPrefix); + my $outCsvFile= $cfgOutputVpdBinDir.$cfgPrefix."_map.csv"; + trace("createMappingFile: $outCsvFile"); + open(CSV_FILE,">$outCsvFile") || + fatal("Couldn't open $outCsvFile: $!"); + print CSV_FILE "MCS,RANK,FREQ,KEYCHAR,VPDFILE\n"; + + # open file for binary version of mapping data + my %fileInfo = newFileInfo($cfgPrefix."_".uc($tarType).".bin", + VPD_BIN_FILE_SIZE ); + + # start with header + # - version + # - number of entries + # - reserved + my %num = newNum(MAPPING_LAYOUT_VERSION,"u8"); + filePushNum(\%fileInfo,\%num); + str2num(\%num,scalar keys %g_configs,"u8"); + filePushNum(\%fileInfo,\%num); + str2num(\%num,0,"u8"); + filePushNum(\%fileInfo,\%num); + + my @keys = sort {$a <=> $b} keys %g_configs; + foreach my $key (@keys) + { + my $ref_config = $g_configs{$key}; + + my $mcsMask = $ref_config->{CONF_MCS}; + my $freqMask = $ref_config->{CONF_FREQ}; + my $rankMask = $ref_config->{CONF_RANK}; + my $keyChar = $ref_config->{CONF_KEY_CHAR}; + my $vpdFile = $ref_config->{CONF_VPD_TEXT_FILE}; + my $binFile = $ref_config->{CONF_BIN_FILE}; + + print CSV_FILE "0x".sprintf("%04X",$mcsMask).",". + "0x".sprintf("%04X",$rankMask).",". + "0x".sprintf("%02X",$freqMask).",". + "0x".sprintf("%02X",$keyChar).",". + chr($keyChar)."-$vpdFile\n"; + + #write bin config line + # - MCS mask + # - Rank mask + # - Frequency mask + # - keyword character + str2num(\%num,$mcsMask,"u16"); + filePushNum(\%fileInfo,\%num); + str2num(\%num,$rankMask,"u16"); + filePushNum(\%fileInfo,\%num); + str2num(\%num,$freqMask,"u8"); + filePushNum(\%fileInfo,\%num); + str2num(\%num,$keyChar,"u8"); + filePushNum(\%fileInfo,\%num); + } + fileWrite(\%fileInfo); + close(CSV_FILE); +} + +# create report +sub createReport +{ + my $numUnf = 0; + my $numOK = 0; + my $numDup = 0; + + # open file for report. Report can be used to validate mapping. + my $outCsvFile= $cfgOutputVpdBinDir.$cfgPrefix."_report.csv"; + open(CSV_FILE,">$outCsvFile") || + fatal("Couldn't open $outCsvFile: $!"); + trace("createReport: $outCsvFile"); + print CSV_FILE "CHK,MCS,FREQ,RANK,BINFILE,VPDFILE\n"; + + my $numMCS = scalar keys %g_mcsMask; + my $numFreq = scalar keys %g_freqMask; + my $numRank = scalar keys %g_rankMask; + + for (my $i=0 ; $i<$numMCS ; $i++) + { + for (my $j=0 ; $j<$numFreq ; $j++) + { + my @keys = sort {$a <=> $b} keys %g_rankMask; + foreach my $key (@keys) + { + my @configList = checkConfig($g_mcsMask{$i}, + $g_freqMask{$j}, + $g_rankMask{$key}); + my $numHits = scalar @configList; + my $status = ""; + if (0 == $numHits) + { + $status = "UNF"; + print CSV_FILE "$status,$i,$g_MrwFreq[$j],". + "0x".sprintf("%02X",$key).',"",""'."\n"; + $numUnf++; + } + else + { + if (1 == $numHits) + { + $status = "OK "; + $numOK++; + } + else + { + $status = "DUP"; + $numDup++; + } + for (my $m=0; $m < $numHits ; $m++) + { + print CSV_FILE "$status,$i,$g_MrwFreq[$j],". + "0x".sprintf("%02X",$key).",". + "$configList[$m]->{CONF_BIN_FILE},". + "$configList[$m]->{CONF_VPD_TEXT_FILE}\n"; + } + } + } + } + } + verbose("Of the ".$numMCS*$numFreq*$numRank." combinations:"); + verbose(" ".sprintf("%4D",$numOK)." are mapped to vpd data"); + verbose(" ".sprintf("%4D",$numUnf)." are undefined"); + verbose(" ".sprintf("%4D",$numDup)." are duplicated (should be 0)"); + if ($numDup) + { + warning("There are $numDup duplicate mappings, there should be none.\n". + " Review $cfgPrefix"."_report.txt and correct the vpd files"); + + } + close(CSV_FILE); +} + +# Return list of configs that this configuration "hits" +# Ideally, it is just one or none. A dup means there is a overlap problem +# in the vpd text files +sub checkConfig +{ + my($mcsMask,$freqMask,$rankMask) = @_; + + my @configList = (); + trace("checkConfig:". + " mcsMask=".sprintf("%04X",$mcsMask). + " freqMask=".sprintf("%02X",$freqMask). + " rankMask=".sprintf("%04X",$rankMask)); + + my @keys = sort {$a <=> $b} keys %g_configs; + foreach my $key (@keys) + { + my $ref_config = $g_configs{$key}; + my $status="UNF"; + + my $mcsHit = $mcsMask & $ref_config->{CONF_MCS}; + my $freqHit = $freqMask & $ref_config->{CONF_FREQ}; + my $rankHit = $rankMask & $ref_config->{CONF_RANK}; + if ($mcsHit && $freqHit && $rankHit) + { + push @configList, $ref_config; + $status="HIT"; + } + trace(" $status==> ".$ref_config->{CONF_BIN_FILE}); + trace(" mscHit=0x".sprintf("%04X",$mcsHit). + " 0x".sprintf("%04X",$ref_config->{CONF_MCS})); + trace(" freqHit=".sprintf("%02X",$freqHit). + " 0x".sprintf("%02X",$ref_config->{CONF_FREQ})); + trace(" rankHit=".sprintf("%04X",$rankHit). + " 0x".sprintf("%04X",$ref_config->{CONF_RANK})); + } + + return @configList; +} + +################################################################################ +# Number helpers +################################################################################ + +# create a "number" hash +sub newNum +{ + my($value,$type) = @_; + + my ($decSize)= $type =~ /u(\d+)/; + my $byteSize = $decSize / 8; + + my %number = (); + $number{NUM_VALUE}=str2value($value); + $number{NUM_SIZE}=$byteSize; + + return %number; +} + +# update a "number" hash +sub str2num +{ + my $ref_num = shift; + my $value = shift; + my $type = shift; + + my ($decSize)= $type =~ /u(\d+)/; + my $byteSize = $decSize / 8; + + $ref_num->{NUM_VALUE}=str2value($value); + $ref_num->{NUM_SIZE}=$byteSize; +} + +# Convert a string into a numeric value scalar +sub str2value +{ + my($text) = @_; + + my $value =eval($text); + if ($value eq undef) + { + fatal("$text is not a number"); + } + + return $value; +} + +################################################################################ +# trace and related helpers +################################################################################ + +# verbose to STDOUT and trace +sub verbose +{ + my($text) = @_; + + if($cfgVerbose) + { + print STDOUT $text."\n"; + } + trace ($text); +} + +# trace +my $static_traceInit=0; +sub trace +{ + my($text) = @_; + + if (0 == $static_traceInit) + { + my $outTextFile= $cfgOutputVpdBinDir.$cfgPrefix."_trace.txt"; + open(TRACE_FILE,">$outTextFile") || + fatal("couldn't open $outTextFile: $!"); + $static_traceInit = 1; + } + print TRACE_FILE $text."\n"; +} + +# add a configuration to the trace +sub traceConfig +{ + my($tag,$ref_config) = @_; + + my $mcsMask = $ref_config->{CONF_MCS}; + my $freqMask = $ref_config->{CONF_FREQ}; + my $rankMask = $ref_config->{CONF_RANK}; + my $keyChar = chr($ref_config->{CONF_KEY_CHAR}); + my $vpdFile = $ref_config->{CONF_VPD_TEXT_FILE}; + my $binFile = $ref_config->{CONF_BIN_FILE}; + + trace("$tag $binFile"); + trace(" key=$keyChar mcsMask=".sprintf("%04X",$mcsMask). + " freqMask=".sprintf("%02X",$freqMask). + " rankMask=".sprintf("%04X",$rankMask)); + trace(" from= $vpdFile"); +} + +#warning error +sub warning +{ + my($text) = @_; + + trace("[WARNING!] $text"); + print STDERR "[WARNING!] $text\n"; +} + + +#fatal error +sub fatal +{ + my($text) = @_; + + trace("[FATAL!] $text"); + print STDERR "[FATAL!] $text\n"; + + exit(1); +} + +# Display the parameters +sub display_help +{ + use File::Basename; + my $scriptname = basename($0); + + print STDERR " +usage: + $scriptname --help + $scriptname --mrw-MEMVPD-FREQS-MHZ --prefix + [--input-vpd-text-dir=./] [--output-vpd-bin-dir=./] + [--verbose] + --mrw-MEMVPD-FREQS-MHZ + Comma separated list of the 4 ATTR_MEMVPD_FREQS_MHZ data rates + --prefix + Prefix of vpd input files to process (template_mr or template_mt) + --input-vpd-text-dir + Optional path to directory with input vpd files. + Defaults to current directory (./) + --output-vpd-bin-dir + Optional path to directory for output binary and support files. + Defaults to current directory (./) + --verbose + Optional additional execution information. + Defaults to not verbose. +\n"; +} -- cgit v1.2.1