%%%============================================================================= %%% 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), <>}. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode(FruId, <<>>) -> {ok, {fru_data, [{fru_id, FruId}]}}; decode(FruId, D = <>) -> 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 = <>) -> decode_chassis(is_binary_sane(Area), Data). decode_chassis(false, _Data) -> []; decode_chassis(true, <>) -> {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 = <>) -> decode_board(is_binary_sane(Area), Data). decode_board(false, _Data) -> []; decode_board(true, <>) -> {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 = <>) -> decode_product(is_binary_sane(Area), Data). decode_product(false, _Data) -> []; decode_product(true, <>) -> {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 = <>) -> decode_record(is_binary_sane(Header), Binary). decode_record(false, _Binary) -> {[], <<>>}; decode_record(true, <>) -> {decode_record(sum(D, C) =:= 0, T, D), <<>>}; decode_record(true, <>) -> {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, <>) -> decode_picmg_record(Rest); decode_record_field_definition( Type, <> ) when Type >= 16#c0 andalso Type =< 16#ff -> [{oem, [{type, Type}, {manufacturer_id, ManufaturerId}, {data, Rest}]}]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ decode_power_supply( <> ) -> <> = <>, <> = <>, [ {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( <> ) -> [ {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( <> ) -> [ {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( <> ) -> 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( <> ) -> decode_amc_p2p_connectivity_record([], GUIDCount, Rest). decode_amc_p2p_connectivity_record(Acc, 0, <>) -> decode_amc_p2p_connectivity_record(Acc, Rest); decode_amc_p2p_connectivity_record(Acc, C, <>) -> NewAcc = Acc ++ [{guid, eipmi_util:binary_to_string(GUID)}], decode_amc_p2p_connectivity_record(NewAcc, C - 1, Rest). decode_amc_p2p_connectivity_record(Acc, <>) -> {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( <> ) -> 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, <> ) -> 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) -> <> = Data, {get_amc_channel_descriptors_(D, []), Rest}. get_amc_channel_descriptors_(<<>>, Acc) -> lists:reverse(Acc); get_amc_channel_descriptors_(<>, Acc) -> NewAcc = [get_amc_channel_descriptor(<>) | Acc], get_amc_channel_descriptors_(Rest, NewAcc). get_amc_channel_descriptor(<>) -> {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(<>, Acc) -> get_amc_link_descs(Rest, [get_amc_link_desc(<>) | Acc]). get_amc_link_desc(<>) -> {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, <>, 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 = <>, Acc) -> {Acc, Rest}; until_eofields(<>, Acc) -> until_eofields(Rest, <>). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ non_zero(List) -> [Elem || Elem <- List, Elem > 0]. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ is_header_sane(<>) -> is_binary_sane(Header). %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ is_binary_sane(Binary) -> sum(Binary, 0) =:= 0. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ sum(<<>>, Sum) -> Sum rem 256; sum(<>, 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 ] ).