Current section
Files
Jump to
Current section
Files
src/eipmi_fru.erl
%%%=============================================================================
%%% Copyright (c) 2012 Lindenbaum GmbH
%%%
%%% Permission to use, copy, modify, and/or distribute this software for any
%%% purpose with or without fee is hereby granted, provided that the above
%%% copyright notice and this permission notice appear in all copies.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%%%
%%% @doc
%%% A module providing FRU reading and decoding functionality according to the
%%% official IPMI Platform Management FRU Information Storage Definition.
%%% @end
%%%=============================================================================
-module(eipmi_fru).
-export([read/2,
to_list/1]).
-include("eipmi.hrl").
-define(OLD_ENGLISH, 0).
-define(ENGLISH, 25).
-define(EOFIELDS, 16#c1).
-define(MAX_READ_COUNT, 23).
-define(READ, {?IPMI_NETFN_STORAGE_REQUEST, ?READ_FRU_DATA}).
-define(GET_INFO, {?IPMI_NETFN_STORAGE_REQUEST, ?GET_FRU_INVENTORY_AREA_INFO}).
-type chassis_info() ::
{type, non_neg_integer()} |
{part_number, string()} |
{serial_number, string()} |
{custom, term()}.
-type board_info() ::
{manufacturing_date, non_neg_integer()} |
{manufacturer, string()} |
{name, string()} |
{serial_number, string()} |
{part_number, string()} |
{fru_file_id, string()} |
{custom, term()}.
-type product_info() ::
board_info() |
{version, string()} |
{asset_tag, string()} |
{custom, term()}.
-type multi_record() ::
{power_supply, proplists:proplist()} |
{dc_output, proplists:proplist()} |
{dc_load, proplists:proplist()} |
{management_access, proplists:proplist()} |
{base_compatibility, proplists:proplist()} |
{extended_compatibility, proplists:proplist()} |
{amc_p2p_connectivity, proplists:proplist()} |
{mtca_carrier_information, proplists:proplist()} |
{oem, proplists:proplist()}.
-type info() ::
{fru_data,
[{fru_id, 0..255} |
{chassis_area, [chassis_info()]} |
{board_area, [board_info()]} |
{product_area, [product_info()]} |
{record_area, [multi_record()]}]}.
-export_type([chassis_info/0,
board_info/0,
product_info/0,
multi_record/0,
info/0]).
%%%=============================================================================
%%% API
%%%=============================================================================
%%------------------------------------------------------------------------------
%% @doc
%% Read and decode FRU inventory data for a specific FRU id using the provided
%% IPMI session. The returned FRU information is a property list that does only
%% contain the available and checksum error free fields of the inventory.
%% If no FRU data is available for a specific id the returned inventory data is
%% the empty list.
%% @end
%%------------------------------------------------------------------------------
-spec read(pid(), 0..254) -> {ok, info()} | {error, term()}.
read(SessionPid, FruId) ->
FruInfo = eipmi_session:rpc(SessionPid, ?GET_INFO, [{fru_id, FruId}]),
do_read(FruInfo, SessionPid, FruId).
%%------------------------------------------------------------------------------
%% @doc
%% Returns a string representation of one or more FRU inventory entries.
%% @end
%%------------------------------------------------------------------------------
-spec to_list(info() | [info()]) -> string().
to_list(FruInventory) when is_list(FruInventory) ->
eipmi_util:join_nl([to_list(FruInfo) || FruInfo <- FruInventory]);
to_list({fru_data, Properties}) ->
FruId = integer_to_list(proplists:get_value(fru_id, Properties, -1)),
eipmi_util:join_nl(
["---------------------------------------",
"FRU Info for device " ++ FruId ++ ":",
"---------------------------------------",
to_list_chassis(proplists:get_value(chassis_area, Properties, [])),
"---------------------------------------",
to_list_board(proplists:get_value(board_area, Properties, [])),
"---------------------------------------",
to_list_product(proplists:get_value(product_area, Properties, []))]).
%%%=============================================================================
%%% Internal functions
%%%=============================================================================
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
do_read({error, {bmc_error, parameter_out_of_range}}, _SessionPid, _FruId) ->
{ok, []};
do_read(Error = {error, _}, _SessionPid, _FruId) ->
Error;
do_read({ok, FruInfo}, SessionPid, FruId) ->
Access = proplists:get_value(access, FruInfo),
Div = case Access of by_words -> 2; by_bytes -> 1 end,
AreaSize = proplists:get_value(area_size, FruInfo) div Div,
decode(FruId, do_read(SessionPid, FruId, AreaSize, ?MAX_READ_COUNT div Div)).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
do_read(SessionPid, FruId, Size, BlockSize) ->
do_read(SessionPid, FruId, Size, BlockSize, {0, <<>>}).
do_read(_SessionPid, _FruId, Size, _BlockSize, {Size, Acc}) ->
Acc;
do_read(SessionPid, FruId, Size, BlockSize, {Offset, Acc})
when Offset + BlockSize =< Size ->
NewAcc = read_raw(SessionPid, FruId, Offset, BlockSize, Acc),
do_read(SessionPid, FruId, Size, BlockSize, NewAcc);
do_read(SessionPid, FruId, Size, BlockSize, {Offset, Acc}) ->
NewAcc = read_raw(SessionPid, FruId, Offset, Size - Offset, Acc),
do_read(SessionPid, FruId, Size, BlockSize, NewAcc).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
read_raw(SessionPid, FruId, Offset, Count, Acc) ->
Ps = [{fru_id, FruId}, {offset, Offset}, {count, Count}],
{ok, R} = eipmi_session:rpc(SessionPid, ?READ, Ps),
Data = proplists:get_value(data, R),
{Offset + proplists:get_value(count, R), <<Acc/binary, Data/binary>>}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode(FruId, <<>>) ->
{ok, {fru_data, [{fru_id, FruId}]}};
decode(FruId, D = <<?EIPMI_RESERVED:4, ?FRU_VERSION:4, O:5/binary, _/binary>>) ->
OffsetTuple = list_to_tuple(binary_to_list(O)),
decode(is_header_sane(D), OffsetTuple, FruId, D);
decode(_FruId, _FruData) ->
{error, unsupported_fru_data}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode(false, _Offsets, _FruId, _FruData) ->
{error, incorrect_header_checksum};
decode(true, {_, C, B, P, M}, FruId, FruData) ->
{ok, {fru_data,
[{fru_id, FruId}]
++ decode_chassis(get_area(C, non_zero([B, P, M]), FruData))
++ decode_board(get_area(B, non_zero([P, M]), FruData))
++ decode_product(get_area(P, non_zero([M]), FruData))
++ decode_multi_record(get_area(M, [], FruData))}}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_chassis(<<>>) ->
[];
decode_chassis(Area = <<?EIPMI_RESERVED:4, ?FRU_VERSION:4, _:8, Data/binary>>) ->
decode_chassis(is_binary_sane(Area), Data).
decode_chassis(false, _Data) ->
[];
decode_chassis(true, <<Type:8, Rest/binary>>) ->
{PartNumber, Rest1} = decode_field(part_number, ?ENGLISH, Rest),
{SerialNumber, Rest2} = decode_field(serial_number, ?ENGLISH, Rest1),
CustomFields = decode_fields(custom, ?ENGLISH, Rest2),
[{chassis_area,
[{type, Type}]
++ PartNumber
++ SerialNumber
++ CustomFields}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_board(<<>>) ->
[];
decode_board(Area = <<?EIPMI_RESERVED:4, ?FRU_VERSION:4, _:8, Data/binary>>) ->
decode_board(is_binary_sane(Area), Data).
decode_board(false, _Data) ->
[];
decode_board(true, <<Lang:8, MfgDate:24/little, Rest/binary>>) ->
{Manufacturer, Rest1} = decode_field(manufacturer, Lang, Rest),
{Name, Rest2} = decode_field(name, Lang, Rest1),
{SerialNumber, Rest3} = decode_field(serial_number, ?ENGLISH, Rest2),
{PartNumber, Rest4} = decode_field(part_number, Lang, Rest3),
{FruFileId, Rest5} = decode_field(fru_file_id, ?ENGLISH, Rest4),
CustomFields = decode_fields(custom, Lang, Rest5),
[{board_area,
[{manufacturing_date, MfgDate}]
++ Manufacturer
++ Name
++ SerialNumber
++ PartNumber
++ FruFileId
++ CustomFields}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_product(<<>>) ->
[];
decode_product(Area = <<?EIPMI_RESERVED:4, ?FRU_VERSION:4, _:8, Data/binary>>) ->
decode_product(is_binary_sane(Area), Data).
decode_product(false, _Data) ->
[];
decode_product(true, <<Lang:8, Rest/binary>>) ->
{Manufacturer, Rest1} = decode_field(manufacturer, Lang, Rest),
{Name, Rest2} = decode_field(name, Lang, Rest1),
{PartNumber, Rest3} = decode_field(part_number, Lang, Rest2),
{Version, Rest4} = decode_field(version, Lang, Rest3),
{SerialNumber, Rest5} = decode_field(serial_number, ?ENGLISH, Rest4),
{AssetTag, Rest6} = decode_field(asset_tag, Lang, Rest5),
{FruFileId, Rest7} = decode_field(fru_file_id, Lang, Rest6),
CustomFields = decode_fields(custom, Lang, Rest7),
[{product_area,
Manufacturer
++ Name
++ PartNumber
++ Version
++ SerialNumber
++ AssetTag
++ FruFileId
++ CustomFields}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_multi_record(<<>>) ->
[];
decode_multi_record(RecordData) ->
decode_multi_record([], RecordData).
decode_multi_record(Records, <<>>) ->
[{record_area, lists:reverse(Records)}];
decode_multi_record(Records, RestData) ->
{Record, NewRestData} = decode_record(RestData),
decode_multi_record(Record ++ Records, NewRestData).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_record(<<>>) ->
{[], <<>>};
decode_record(Binary = <<Header:5/binary, _/binary>>) ->
decode_record(is_binary_sane(Header), Binary).
decode_record(false, _Binary) ->
{[], <<>>};
decode_record(true, <<T:8, 1:1, _:7, L:8, C:8, _:8, D:L/binary, _/binary>>) ->
{decode_record(sum(D, C) =:= 0, T, D), <<>>};
decode_record(true, <<T:8, 0:1, _:7, L:8, C:8, _:8, D:L/binary, R/binary>>) ->
{decode_record(sum(D, C) =:= 0, T, D), R}.
decode_record(false, _Type, _Data) ->
[];
decode_record(true, Type, Data) ->
decode_record_field_definition(Type, Data).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_record_field_definition(16#00, Data) ->
[{power_supply, decode_power_supply(Data)}];
decode_record_field_definition(16#01, Data) ->
[{dc_output, decode_dc_output(Data)}];
decode_record_field_definition(16#02, Data) ->
[{dc_load, decode_dc_load(Data)}];
decode_record_field_definition(16#03, Data) ->
[{management_access, decode_management_access(Data)}];
decode_record_field_definition(16#04, Data) ->
[{base_compatibility, decode_compatibility(Data)}];
decode_record_field_definition(16#05, Data) ->
[{extended_compatibility, decode_compatibility(Data)}];
decode_record_field_definition(16#c0, <<?PICMG_MID:24/little, Rest/binary>>) ->
decode_picmg_record(Rest);
decode_record_field_definition(Type, <<ManufaturerId:24/little, Rest/binary>>)
when Type >= 16#c0 andalso Type =< 16#ff ->
[{oem, [{type, Type}, {manufacturer_id, ManufaturerId}, {data, Rest}]}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_power_supply(<<OCapacity:16/little, PeakVA:16/little,
InrushCurrent:8, InrushInterval:8,
LowEndInputV1:16/little, HighEndInputV1:16/little,
LowEndInputV2:16/little, HighEndInputV2:16/little,
LowEndInputFrequency:8, HighEndInputFrequency:8,
ACDropOutTolerance:8, 0:3, FailSignal:1,
HotSwap:1, Autoswitch:1, PowerFactor:1,
PredictiveFailSupport:1, PeakW:16/little,
CombinedWattageV1:4, CombinedWattageV2:4,
TotalWattage:16/little,
PredictiveFailTachometerLowerThreshold:8>>) ->
<<?EIPMI_RESERVED:4, OverallCapacity:12>> = <<OCapacity:16>>,
<<PeakWattageSecs:4, PeakWattage:12>> = <<PeakW:16>>,
[{overall_capacitly, {OverallCapacity, eipmi_sensor:get_unit(6)}},
{peak_voltage, maybe_value({PeakVA, unit(9)}, unspecified)},
{inrush_current, maybe_value({InrushCurrent, unit(5)}, unspecified)},
{inrush_interval, maybe_value({InrushInterval, unit(21)}, unspecified)},
{low_end_input_voltage_range_110v, {LowEndInputV1 / 100, unit(4)}},
{high_end_input_voltage_range_110v, {HighEndInputV1 / 100, unit(4)}},
{low_end_input_voltage_range_220v, {LowEndInputV2 / 100, unit(4)}},
{high_end_input_voltage_range_220v, {HighEndInputV2 / 100, unit(4)}},
{low_end_input_frequency_range, {LowEndInputFrequency, unit(19)}},
{high_end_input_frequency_range, {HighEndInputFrequency, unit(19)}},
{ac_dropout_tolerance, {ACDropOutTolerance, unit(21)}},
{predictive_fail_signal,
get_power_supply_predictive_fail_signal(FailSignal)},
{hot_swap_supported, eipmi_util:get_bool(HotSwap)},
{autoswitch_supported, eipmi_util:get_bool(Autoswitch)},
{power_factor_correction_supported, eipmi_util:get_bool(PowerFactor)},
{predictive_fail_pin_supported, eipmi_util:get_bool(PredictiveFailSupport)},
get_predictive_fail_tachometer_lower_threshold(
PredictiveFailTachometerLowerThreshold),
{peak_wattage_tolerance, {PeakWattageSecs, unit(22)}},
{peak_wattage, {PeakWattage, unit(6)}},
get_combined_wattage(CombinedWattageV1, CombinedWattageV2, TotalWattage)].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_dc_output(<<Standby:1, ?EIPMI_RESERVED:3, OutputNumber:4,
NominalVoltage:16/signed-little,
MaximumNegativeVoltageDeviation:16/signed-little,
MaximumPositiveVoltageDeviation:16/signed-little,
RippleAndNoise:16/little,
MinimumCurrentDraw:16/little,
MaximumCurrentDraw:16/little>>) ->
[{provides_standby_output, eipmi_util:get_bool(Standby)},
{output, maybe_value({OutputNumber, number}, unknown)},
{nominal_voltage, {NominalVoltage / 100, unit(4)}},
{maximum_negative_voltage_deviation,
{MaximumNegativeVoltageDeviation / 100, unit(4)}},
{maximum_positive_voltage_deviation,
{MaximumPositiveVoltageDeviation / 100, unit(4)}},
{ripple_and_noise, {RippleAndNoise / 1000, unit(4)}},
{minimum_current_draw, {MinimumCurrentDraw / 1000, unit(5)}},
{maximum_current_draw, {MaximumCurrentDraw / 1000, unit(5)}}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_dc_load(<<?EIPMI_RESERVED:4, OutputNumber:4,
NominalVoltage:16/signed-little,
MinimumVoltage:16/signed-little,
MaximumVoltage:16/signed-little,
RippleAndNoise:16/little,
MinimumCurrentLoad:16/little,
MaximumCurrentLoad:16/little>>) ->
[{output, maybe_value({OutputNumber, number}, unknown)},
{nominal_voltage, {NominalVoltage / 100, unit(4)}},
{minimum_voltage, {MinimumVoltage / 100, unit(4)}},
{maximum_voltage, {MaximumVoltage / 100, unit(4)}},
{ripple_and_noise, {RippleAndNoise / 1000, unit(4)}},
{minimum_current_load, {MinimumCurrentLoad / 1000, unit(5)}},
{maximum_current_load, {MaximumCurrentLoad / 1000, unit(5)}}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_management_access(<<16#01:8, URL/binary>>) ->
[{system_management_url, eipmi_util:binary_to_string(URL)}];
decode_management_access(<<16#02:8, Name/binary>>) ->
[{system_name, eipmi_util:binary_to_string(Name)}];
decode_management_access(<<16#03:8, Addr/binary>>) ->
[{system_ping_address, eipmi_util:binary_to_string(Addr)}];
decode_management_access(<<16#04:8, URL/binary>>) ->
[{component_management_url, eipmi_util:binary_to_string(URL)}];
decode_management_access(<<16#05:8, Name/binary>>) ->
[{component_name, eipmi_util:binary_to_string(Name)}];
decode_management_access(<<16#06:8, Addr/binary>>) ->
[{component_ping_address, eipmi_util:binary_to_string(Addr)}];
decode_management_access(<<16#07:8, Id/binary>>) ->
[{system_unique_id, eipmi_util:binary_to_string(Id)}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_compatibility(<<ManufacturerID:24/little, EntityID:8,
CompatibilityBase:8, ?EIPMI_RESERVED:1,
CodeStart:7, CodeRanges/binary>>) ->
eipmi_sensor:get_entity_id(EntityID)
++ [{manufacturer_id, ManufacturerID},
{compatibility_base, CompatibilityBase},
{compatible_codes, get_code_ranges(CodeRanges, CodeStart)}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_picmg_record(<<16#19:8, Rest/binary>>) ->
[{amc_p2p_connectivity, decode_amc_p2p_connectivity_record(Rest)}];
decode_picmg_record(<<16#22:8, Rest/binary>>) ->
[{mtca_carrier_information, decode_mtca_carrier_information_record(Rest)}];
decode_picmg_record(Data) ->
[{oem, [{type, 16#c0}, {manufacturer_id, ?PICMG_MID}, {data, Data}]}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_amc_p2p_connectivity_record(
<<?PICMG_FRU_VERSION:8, GUIDCount:8, Rest/binary>>) ->
decode_amc_p2p_connectivity_record([], GUIDCount, Rest).
decode_amc_p2p_connectivity_record(Acc, 0, <<Rest/binary>>) ->
decode_amc_p2p_connectivity_record(Acc, Rest);
decode_amc_p2p_connectivity_record(Acc, C, <<GUID:16/binary, Rest/binary>>) ->
NewAcc = Acc ++ [{guid, eipmi_util:binary_to_string(GUID)}],
decode_amc_p2p_connectivity_record(NewAcc, C - 1, Rest).
decode_amc_p2p_connectivity_record(Acc, <<D:1/binary, C:8, Rest/binary>>) ->
{ChanDescs, NewRest} = get_amc_channel_descriptors(C * 3, Rest),
LinkDescs = get_amc_link_descs(NewRest),
Acc ++ get_device(D) ++ ChanDescs ++ LinkDescs.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_mtca_carrier_information_record(
<<?PICMG_FRU_VERSION:8, C:8, O:1, _:7, Rest/binary>>) ->
Slots = decode_mtca_carrier_information_record([], Rest),
case C of 16#ff -> []; _ -> [{carrier_number, C}] end
++ get_mtca_orientation(O)
++ [{amc_slots, length(get_mtca_slots(amc, Slots))}]
++ [{mch_slots, length(get_mtca_slots(mch, Slots))}]
++ Slots.
decode_mtca_carrier_information_record(Acc, <<>>) ->
Acc;
decode_mtca_carrier_information_record(
Acc, <<SiN:8, SiT:8, SlN:8, TiN:8, Y:16/little, X:16/little, Rest/binary>>) ->
decode_mtca_carrier_information_record(
Acc ++ [{slot, eipmi_response:get_picmg_site_type(SiT) ++
[{site_number, SiN},
{slot_number, SlN},
{tier_number, TiN},
{x, {X, eipmi_sensor:get_unit(32)}},
{y, {Y, eipmi_sensor:get_unit(32)}}]}], Rest).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_code_ranges(Bytes, CodeStart) ->
lists:sort(get_code_ranges(Bytes, CodeStart, 0, [CodeStart])).
get_code_ranges(<<>>, _CodeStart, _BitIndex, Acc) ->
lists:reverse(Acc);
get_code_ranges(<<1:1, Rest/bitstring>>, CodeStart, BitIndex, Acc) ->
NewCode = CodeStart + (((BitIndex div 8) * 8) + ((8 - (BitIndex rem 8)))),
get_code_ranges(Rest, CodeStart, BitIndex + 1, [NewCode|Acc]);
get_code_ranges(<<0:1, Rest/bitstring>>, CodeStart, BitIndex, Acc) ->
get_code_ranges(Rest, CodeStart, BitIndex + 1, Acc).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_predictive_fail_tachometer_lower_threshold(0) ->
{predictive_fail_lower_treshold, none};
get_predictive_fail_tachometer_lower_threshold(Value) ->
{predictive_fail_lower_treshold, {60 * Value, unit(18)}}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_combined_wattage(0, 0, 0) ->
{combined_wattage, none};
get_combined_wattage(A, B, Wattage) ->
{combined_wattage, {get_voltage(A), get_voltage(B), {Wattage, unit(6)}}}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_voltage(0) -> {12, unit(4)};
get_voltage(1) -> {-12, unit(4)};
get_voltage(2) -> {5, unit(4)};
get_voltage(3) -> {3.3, unit(4)}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_power_supply_predictive_fail_signal(0) ->
one_pulse_per_rotation_or_signal_asserted;
get_power_supply_predictive_fail_signal(1) ->
two_pulses_per_rotation_or_signal_deasserted.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_device(<<1:1, ?EIPMI_RESERVED:7>>) ->
[{device_type, amc}];
get_device(<<0:1, ?EIPMI_RESERVED:3, Id:4>>) ->
[{device_type, on_carrier_device}, {device_id, Id}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_amc_channel_descriptors(Count, Data) ->
<<D:Count/binary, Rest/binary>> = Data,
{get_amc_channel_descriptors_(D, []), Rest}.
get_amc_channel_descriptors_(<<>>, Acc) ->
lists:reverse(Acc);
get_amc_channel_descriptors_(<<Desc:24/little, Rest/binary>>, Acc) ->
NewAcc = [get_amc_channel_descriptor(<<Desc:24>>) | Acc],
get_amc_channel_descriptors_(Rest, NewAcc).
get_amc_channel_descriptor(<<?EIPMI_RESERVED:4, L3:5, L2:5, L1:5, L0:5>>) ->
{amc_channel_descriptor,
case L0 of 16#1f -> []; _ -> [{lane0_port, L0}] end
++ case L1 of 16#1f -> []; _ -> [{lane1_port, L1}] end
++ case L2 of 16#1f -> []; _ -> [{lane2_port, L2}] end
++ case L3 of 16#1f -> []; _ -> [{lane3_port, L3}] end}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_amc_link_descs(Binary) ->
get_amc_link_descs(Binary, []).
get_amc_link_descs(<<>>, Acc) ->
lists:reverse(Acc);
get_amc_link_descs(<<Desc:40/little, Rest/binary>>, Acc) ->
get_amc_link_descs(Rest, [get_amc_link_desc(<<Desc:40>>) | Acc]).
get_amc_link_desc(<<?EIPMI_RESERVED:8, G:8, LTExt:4, LT:8, Ls:4, Id:8>>) ->
{amc_link_descriptor,
[{channel_id, Id}, {group_id, G}]
++ get_amc_lanes(Ls) ++ get_link_type(LT, LTExt)}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_amc_lanes(Lanes) ->
A = case Lanes band 2#1000 bsr 3 of 1 -> [3]; _ -> [] end,
B = case Lanes band 2#100 bsr 2 of 1 -> [2]; _ -> [] end,
C = case Lanes band 2#10 bsr 1 of 1 -> [1]; _ -> [] end,
D = case Lanes band 2#1 of 1 -> [0]; _ -> [] end,
[{lanes, A ++ B ++ C ++ D}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_link_type(16#02, Ext) -> [{link_type, {pci_express, Ext}}];
get_link_type(16#03, Ext) -> [{link_type, {pci_express_advanced_switching, Ext}}];
get_link_type(16#04, Ext) -> [{link_type, {pci_express_advanced_switching, Ext}}];
get_link_type(16#05, Ext) -> [{link_type, {ethernet, Ext}}];
get_link_type(16#06, Ext) -> [{link_type, {serial_rapid_io, Ext}}];
get_link_type(16#07, Ext) -> [{link_type, {storage, Ext}}];
get_link_type(T, Ext) when T >= 16#f0 andalso T =< 16#fe ->
[{link_type, {ekeying, Ext}}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_mtca_orientation(0) ->
[{slot_orientation, left_to_right}, {tier_orientation, bottom_to_top}];
get_mtca_orientation(1) ->
[{slot_orientation, bottom_to_top}, {tier_orientation, left_to_right}].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_mtca_slots(Type, Slots) ->
[S || S = {slot, Ps} <- Slots, proplists:get_value(site_type, Ps) =:= Type].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
maybe_value({0, _}, Alternative) -> Alternative;
maybe_value({16#ffff, _}, Alternative) -> Alternative;
maybe_value({16#ff, _}, Alternative) -> Alternative;
maybe_value(Value, _) -> Value.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
get_area(0, _NextOffsets, _FruData) ->
<<>>;
get_area(Offset, [], FruData) ->
SkipSize = Offset * 8,
<<_:SkipSize/binary, Area/binary>> = FruData,
Area;
get_area(Offset, [NextOffset | _], FruData) ->
SkipSize = Offset * 8,
Size = NextOffset * 8 - SkipSize,
<<_:SkipSize/binary, Area:Size/binary, _/binary>> = FruData,
Area.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_fields(Name, Lang, Data) ->
decode_fields(Name, Lang, Data, []).
decode_fields(_Name, _Lang, <<?EOFIELDS:8, _/binary>>, Acc) ->
lists:reverse(Acc);
decode_fields(Name, Lang, Data, Acc) ->
{Field, Rest} = decode_field(Name, Lang, Data),
decode_fields(Name, Lang, Rest, [Field | Acc]).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
decode_field(_Name, _Lang, <<_:2, 0:6, Rest/binary>>) ->
{[], Rest};
decode_field(Name, _Lang, <<0:2, Len:6, Data:Len/binary, Rest/binary>>) ->
{[{Name, Data}], Rest};
decode_field(Name, _Lang, <<1:2, Len:6, Data:Len/binary, Rest/binary>>) ->
{[{Name, eipmi_util:from_bcd_plus(Data)}], Rest};
decode_field(Name, _Lang, <<2:2, Len:6, Data:Len/binary, Rest/binary>>) ->
{[{Name, eipmi_util:from_packed_ascii(Data)}], Rest};
decode_field(Name, Lang, <<3:2, Len:6, Data:Len/binary, Rest/binary>>)
when Lang =:= ?ENGLISH orelse Lang =:= ?OLD_ENGLISH ->
{[{Name, eipmi_util:binary_to_string(Data)}], Rest};
decode_field(Name, _Lang, <<3:2, _Len:6, Data/utf16-little, Rest/binary>>) ->
{[{Name, Data}], Rest};
%% This clause supports broken custom fields that deliver bullshit in their
%% length field. But note, at least the END-OF-FIELDS indicator must be present.
decode_field(Name, _Lang, <<_Broken:8, Complete/binary>>) ->
{Data, Rest} = until_eofields(Complete),
{[{Name, Data}], Rest}.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
until_eofields(Data) ->
until_eofields(Data, <<>>).
until_eofields(Rest = <<?EOFIELDS:8, _/binary>>, Acc) ->
{Acc, Rest};
until_eofields(<<Byte:8, Rest/binary>>, Acc) ->
until_eofields(Rest, <<Acc/binary, Byte:8>>).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
non_zero(List) -> [Elem || Elem <- List, Elem > 0].
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
is_header_sane(<<Header:8/binary, _/binary>>) -> is_binary_sane(Header).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
is_binary_sane(Binary) -> sum(Binary, 0) =:= 0.
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
sum(<<>>, Sum) -> Sum rem 256;
sum(<<Byte:8, Rest/binary>>, Sum) -> sum(Rest, Sum + Byte).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
unit(Unit) -> eipmi_sensor:get_unit(Unit).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
to_list_chassis([]) -> "Chassis Info Area : -";
to_list_chassis(Area) ->
Type = proplists:get_value(type, Area, "-"),
Part = proplists:get_value(part_number, Area, "-"),
Serial = proplists:get_value(serial_number, Area, "-"),
eipmi_util:join_nl(
["Chassis Info Area",
"Chassis Type : " ++ eipmi_util:format("~p", [Type]),
"Chassis Part Number : " ++ Part,
"Chassis Serial Number : " ++ Serial]).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
to_list_board([]) -> "Board Info Area : -";
to_list_board(Area) ->
Manufacturer = proplists:get_value(manufacturer, Area, "-"),
Name = proplists:get_value(name, Area, "-"),
Serial = proplists:get_value(serial_number, Area, "-"),
Part = proplists:get_value(part_number, Area, "-"),
File = proplists:get_value(fru_file_id, Area, "-"),
eipmi_util:join_nl(
["Board Info Area",
"Manufacturer : " ++ Manufacturer,
"Board Name : " ++ Name,
"Serial Number : " ++ Serial,
"Part Number : " ++ Part,
"FRU file ID : " ++ File]).
%%------------------------------------------------------------------------------
%% @private
%%------------------------------------------------------------------------------
to_list_product([]) -> "Product Info Area : -";
to_list_product(Area) ->
Manufacturer = proplists:get_value(manufacturer, Area, "-"),
Name = proplists:get_value(name, Area, "-"),
Serial = proplists:get_value(serial_number, Area, "-"),
Part = proplists:get_value(part_number, Area, "-"),
File = proplists:get_value(fru_file_id, Area, "-"),
Version = proplists:get_value(version, Area, "-"),
Tag = proplists:get_value(asset_tag, Area, "-"),
eipmi_util:join_nl(
["Product Info Area",
"Manufacturer : " ++ Manufacturer,
"Product Name : " ++ Name,
"Product Number : " ++ Part,
"Part Version : " ++ Version,
"Product Serial Number : " ++ Serial,
"Asset Tag : " ++ Tag,
"FRU file ID : " ++ File]).