2020-05-21 06:37:51 +02:00
|
|
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-08-28 00:59:19 +02:00
|
|
|
"bytes"
|
2020-05-21 06:37:51 +02:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This program generates de-duplicated SPD files for LPDDR4x memory using the global memory
|
|
|
|
* part list provided in CSV format. In addition to that, it also generates SPD manifest in CSV
|
|
|
|
* format that contains entries of type (DRAM part name, SPD file name) which provides the SPD
|
|
|
|
* file name used by a given DRAM part.
|
|
|
|
*
|
|
|
|
* It takes as input:
|
|
|
|
* Pointer to directory where the generated SPD files will be placed.
|
|
|
|
* JSON file containing a list of memory parts with their attributes as per datasheet.
|
|
|
|
*/
|
|
|
|
const (
|
2020-08-21 01:19:44 +02:00
|
|
|
SPDManifestFileName = "lp4x_spd_manifest.generated.txt"
|
2020-05-21 06:37:51 +02:00
|
|
|
|
|
|
|
PlatformTGL = 0
|
|
|
|
PlatformJSL = 1
|
|
|
|
)
|
|
|
|
|
|
|
|
var platformMap = map[string]int {
|
|
|
|
"TGL": PlatformTGL,
|
|
|
|
"JSL": PlatformJSL,
|
|
|
|
}
|
|
|
|
|
|
|
|
var currPlatform int
|
|
|
|
|
|
|
|
type memAttributes struct {
|
|
|
|
/* Primary attributes - must be provided by JSON file for each part */
|
|
|
|
DensityPerChannelGb int
|
|
|
|
Banks int
|
|
|
|
ChannelsPerDie int
|
|
|
|
DiesPerPackage int
|
|
|
|
BitWidthPerChannel int
|
|
|
|
RanksPerChannel int
|
|
|
|
SpeedMbps int
|
|
|
|
|
|
|
|
/*
|
|
|
|
* All the following parameters are optional and required only if the part requires
|
|
|
|
* special parameters as per the datasheet.
|
|
|
|
*/
|
|
|
|
/* Timing parameters */
|
|
|
|
TRFCABNs int
|
|
|
|
TRFCPBNs int
|
|
|
|
TRPABMinNs int
|
|
|
|
TRPPBMinNs int
|
|
|
|
TCKMinPs int
|
|
|
|
TCKMaxPs int
|
|
|
|
TAAMinPs int
|
|
|
|
TRCDMinNs int
|
|
|
|
|
|
|
|
/* CAS */
|
|
|
|
CASLatencies string
|
|
|
|
CASFirstByte byte
|
|
|
|
CASSecondByte byte
|
|
|
|
CASThirdByte byte
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This encodes the density in Gb to SPD values as per JESD 21-C */
|
|
|
|
var densityGbToSPDEncoding = map[int]byte {
|
|
|
|
4: 0x4,
|
|
|
|
6: 0xb,
|
|
|
|
8: 0x5,
|
|
|
|
12: 0x8,
|
|
|
|
16: 0x6,
|
|
|
|
24: 0x9,
|
|
|
|
32: 0x7,
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Table 3 from JESD209-4C.
|
|
|
|
* Maps density per physical channel to row-column encoding as per JESD 21-C for a device with
|
|
|
|
* x16 physical channel.
|
|
|
|
*/
|
|
|
|
var densityGbx16ChannelToRowColumnEncoding = map[int]byte {
|
|
|
|
4: 0x19, /* 15 rows, 10 columns */
|
|
|
|
6: 0x21, /* 16 rows, 10 columns */
|
|
|
|
8: 0x21, /* 16 rows, 10 columns */
|
|
|
|
12: 0x29, /* 17 rows, 10 columns */
|
|
|
|
16: 0x29, /* 17 rows, 10 columns */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Table 5 from JESD209-4C.
|
|
|
|
* Maps density per physical channel to row-column encoding as per JESD 21-C for a device with
|
|
|
|
* x8 physical channel.
|
|
|
|
*/
|
|
|
|
var densityGbx8ChannelToRowColumnEncoding = map[int]byte {
|
|
|
|
3: 0x21, /* 16 rows, 10 columns */
|
|
|
|
4: 0x21, /* 16 rows, 10 columns */
|
|
|
|
6: 0x29, /* 17 rows, 10 columns */
|
|
|
|
8: 0x29, /* 17 rows, 10 columns */
|
|
|
|
12: 0x31, /* 18 rows, 10 columns */
|
|
|
|
16: 0x31, /* 18 rows, 10 columns */
|
|
|
|
}
|
|
|
|
|
|
|
|
type refreshTimings struct {
|
|
|
|
TRFCABNs int
|
|
|
|
TRFCPBNs int
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Table 112 from JESD209-4C
|
|
|
|
* Maps density per physical channel to refresh timings. This is the same for x8 and x16
|
|
|
|
* devices.
|
|
|
|
*/
|
|
|
|
var densityGbPhysicalChannelToRefreshEncoding = map[int]refreshTimings {
|
|
|
|
3: {
|
|
|
|
TRFCABNs: 180,
|
|
|
|
TRFCPBNs: 90,
|
|
|
|
},
|
|
|
|
4: {
|
|
|
|
TRFCABNs: 180,
|
|
|
|
TRFCPBNs: 90,
|
|
|
|
},
|
|
|
|
6: {
|
|
|
|
TRFCABNs: 280,
|
|
|
|
TRFCPBNs: 140,
|
|
|
|
},
|
|
|
|
8: {
|
|
|
|
TRFCABNs: 280,
|
|
|
|
TRFCPBNs: 140,
|
|
|
|
},
|
|
|
|
12: {
|
|
|
|
TRFCABNs: 380,
|
|
|
|
TRFCPBNs: 190,
|
|
|
|
},
|
|
|
|
16: {
|
|
|
|
TRFCABNs: 380,
|
|
|
|
TRFCPBNs: 190,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
type speedParams struct {
|
|
|
|
TCKMinPs int
|
|
|
|
TCKMaxPs int
|
|
|
|
CASLatenciesx16Channel string
|
|
|
|
CASLatenciesx8Channel string
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
/* First Byte */
|
|
|
|
CAS6 = 1 << 1
|
|
|
|
CAS10 = 1 << 4
|
|
|
|
CAS14 = 1 << 7
|
|
|
|
/* Second Byte */
|
|
|
|
CAS16 = 1 << 0
|
|
|
|
CAS20 = 1 << 2
|
|
|
|
CAS22 = 1 << 3
|
|
|
|
CAS24 = 1 << 4
|
|
|
|
CAS26 = 1 << 5
|
|
|
|
CAS28 = 1 << 6
|
|
|
|
/* Third Byte */
|
|
|
|
CAS32 = 1 << 0
|
|
|
|
CAS36 = 1 << 2
|
|
|
|
CAS40 = 1 << 4
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
/*
|
|
|
|
* JEDEC spec says that TCKmax should be 100ns for all speed grades.
|
|
|
|
* 100ns in MTB units comes out to be 0x320. But since this is a byte field, set it to
|
|
|
|
* 0xFF i.e. 31.875ns.
|
|
|
|
*/
|
|
|
|
TCKMaxPsDefault = 31875
|
|
|
|
)
|
|
|
|
|
|
|
|
var speedMbpsToSPDEncoding = map[int]speedParams {
|
|
|
|
4267: {
|
|
|
|
TCKMinPs: 468, /* 1/4267 * 2 */
|
|
|
|
TCKMaxPs: TCKMaxPsDefault,
|
|
|
|
CASLatenciesx16Channel: "6 10 14 20 24 28 32 36",
|
|
|
|
CASLatenciesx8Channel: "6 10 16 22 26 32 36 40",
|
|
|
|
},
|
|
|
|
3733: {
|
|
|
|
TCKMinPs: 535, /* 1/3733 * 2 */
|
|
|
|
TCKMaxPs: TCKMaxPsDefault,
|
|
|
|
CASLatenciesx16Channel: "6 10 14 20 24 28 32",
|
|
|
|
CASLatenciesx8Channel: "6 10 16 22 26 32 36",
|
|
|
|
},
|
|
|
|
3200: {
|
|
|
|
TCKMinPs: 625, /* 1/3200 * 2 */
|
|
|
|
TCKMaxPs: TCKMaxPsDefault,
|
|
|
|
CASLatenciesx16Channel: "6 10 14 20 24 28",
|
|
|
|
CASLatenciesx8Channel: "6 10 16 22 26 32",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
var bankEncoding = map[int]byte {
|
|
|
|
4: 0 << 4,
|
|
|
|
8: 1 << 4,
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
TGLLogicalChannelWidth = 16
|
|
|
|
)
|
|
|
|
|
|
|
|
/* Returns density to encode as per Intel MRC expectations. */
|
|
|
|
func getMRCDensity(memAttribs *memAttributes) int {
|
|
|
|
if currPlatform == PlatformTGL {
|
|
|
|
/*
|
|
|
|
* Intel MRC on TGL expects density per logical channel to be encoded in
|
|
|
|
* SPDIndexDensityBanks. Logical channel on TGL is an x16 channel.
|
|
|
|
*/
|
|
|
|
return memAttribs.DensityPerChannelGb * TGLLogicalChannelWidth / memAttribs.BitWidthPerChannel
|
|
|
|
} else if currPlatform == PlatformJSL {
|
|
|
|
/*
|
|
|
|
* Intel MRC on JSL expects density per die to be encoded in
|
|
|
|
* SPDIndexDensityBanks.
|
|
|
|
*/
|
|
|
|
return memAttribs.DensityPerChannelGb * memAttribs.ChannelsPerDie
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeDensityBanks(memAttribs *memAttributes) byte {
|
|
|
|
var b byte
|
|
|
|
|
|
|
|
b = densityGbToSPDEncoding[getMRCDensity(memAttribs)]
|
|
|
|
b |= bankEncoding[memAttribs.Banks]
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeSdramAddressing(memAttribs *memAttributes) byte {
|
|
|
|
densityPerChannelGb := memAttribs.DensityPerChannelGb
|
|
|
|
if memAttribs.BitWidthPerChannel == 8 {
|
|
|
|
return densityGbx8ChannelToRowColumnEncoding[densityPerChannelGb]
|
|
|
|
} else {
|
|
|
|
return densityGbx16ChannelToRowColumnEncoding[densityPerChannelGb]
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeChannelsPerDie(channels int) byte {
|
|
|
|
var temp byte
|
|
|
|
|
|
|
|
temp = byte(channels >> 1)
|
|
|
|
|
|
|
|
return temp << 2
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodePackage(dies int) byte {
|
|
|
|
var temp byte
|
|
|
|
|
|
|
|
if dies > 1 {
|
|
|
|
/* If more than one die, then this is a non-monolithic device. */
|
|
|
|
temp = 1
|
|
|
|
} else {
|
|
|
|
/* If only single die, then this is a monolithic device. */
|
|
|
|
temp = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
return temp << 7
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeDiesPerPackage(memAttribs *memAttributes) byte {
|
|
|
|
var dies int = 0
|
|
|
|
if currPlatform == PlatformTGL {
|
|
|
|
/* Intel MRC expects logical dies to be encoded for TGL. */
|
|
|
|
dies = memAttribs.ChannelsPerDie * memAttribs.RanksPerChannel * memAttribs.BitWidthPerChannel / 16
|
|
|
|
} else if currPlatform == PlatformJSL {
|
|
|
|
/* Intel MRC expects physical dies to be encoded for JSL. */
|
|
|
|
dies = memAttribs.DiesPerPackage
|
|
|
|
}
|
|
|
|
|
|
|
|
b := encodePackage(dies) /* Monolithic / Non-monolithic device */
|
|
|
|
b |= (byte(dies) - 1) << 4
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodePackageType(memAttribs *memAttributes) byte {
|
|
|
|
var b byte
|
|
|
|
|
|
|
|
b |= encodeChannelsPerDie(memAttribs.ChannelsPerDie)
|
|
|
|
b |= encodeDiesPerPackage(memAttribs)
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeDataWidth(bitWidthPerChannel int) byte {
|
|
|
|
return byte(bitWidthPerChannel / 8)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeRanks(ranks int) byte {
|
|
|
|
var b byte
|
|
|
|
b = byte(ranks - 1)
|
|
|
|
return b << 3
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeModuleOrganization(memAttribs *memAttributes) byte {
|
|
|
|
var b byte
|
|
|
|
|
|
|
|
b = encodeDataWidth(memAttribs.BitWidthPerChannel)
|
|
|
|
b |= encodeRanks(memAttribs.RanksPerChannel)
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
/*
|
|
|
|
* As per advisory 616599:
|
|
|
|
* 7:5 (Number of system channels) = 000 (1 channel always)
|
|
|
|
* 2:0 (Bus width) = 001 (x16 always)
|
|
|
|
* Set to 0x01.
|
|
|
|
*/
|
|
|
|
SPDValueBusWidthTGL = 0x01
|
|
|
|
/*
|
|
|
|
* As per advisory 610202:
|
|
|
|
* 7:5 (Number of system channels) = 001 (2 channel always)
|
|
|
|
* 2:0 (Bus width) = 010 (x32 always)
|
|
|
|
* Set to 0x01.
|
|
|
|
*/
|
|
|
|
SPDValueBusWidthJSL = 0x22
|
|
|
|
)
|
|
|
|
|
|
|
|
func encodeBusWidth(memAttribs *memAttributes) byte {
|
|
|
|
if currPlatform == PlatformTGL {
|
|
|
|
return SPDValueBusWidthTGL
|
|
|
|
} else if currPlatform == PlatformJSL {
|
|
|
|
return SPDValueBusWidthJSL
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTCKMin(memAttribs *memAttributes) byte {
|
|
|
|
return convPsToMtbByte(memAttribs.TCKMinPs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTCKMinFineOffset(memAttribs *memAttributes) byte {
|
|
|
|
return convPsToFtbByte(memAttribs.TCKMinPs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTCKMax(memAttribs *memAttributes) byte {
|
|
|
|
return convPsToMtbByte(memAttribs.TCKMaxPs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTCKMaxFineOffset(memAttribs *memAttributes) byte {
|
|
|
|
return convPsToFtbByte(memAttribs.TCKMaxPs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeCASFirstByte(memAttribs *memAttributes) byte {
|
|
|
|
return memAttribs.CASFirstByte
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeCASSecondByte(memAttribs *memAttributes) byte {
|
|
|
|
return memAttribs.CASSecondByte
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeCASThirdByte(memAttribs *memAttributes) byte {
|
|
|
|
return memAttribs.CASThirdByte
|
|
|
|
}
|
|
|
|
|
|
|
|
func divRoundUp(dividend int, divisor int) int {
|
|
|
|
return (dividend + divisor - 1) / divisor
|
|
|
|
}
|
|
|
|
|
|
|
|
func convNsToPs(timeNs int) int {
|
|
|
|
return timeNs * 1000
|
|
|
|
}
|
|
|
|
|
|
|
|
func convMtbToPs(mtb int) int {
|
|
|
|
return mtb * 125
|
|
|
|
}
|
|
|
|
|
|
|
|
func convPsToMtb(timePs int) int {
|
|
|
|
return divRoundUp(timePs, 125)
|
|
|
|
}
|
|
|
|
|
|
|
|
func convPsToMtbByte(timePs int) byte {
|
|
|
|
return byte(convPsToMtb(timePs) & 0xff)
|
|
|
|
}
|
|
|
|
|
|
|
|
func convPsToFtbByte(timePs int) byte {
|
|
|
|
mtb := convPsToMtb(timePs)
|
|
|
|
ftb := timePs - convMtbToPs(mtb)
|
|
|
|
|
|
|
|
return byte(ftb)
|
|
|
|
}
|
|
|
|
|
|
|
|
func convNsToMtb(timeNs int) int {
|
|
|
|
return convPsToMtb(convNsToPs(timeNs))
|
|
|
|
}
|
|
|
|
|
|
|
|
func convNsToMtbByte(timeNs int) byte {
|
|
|
|
return convPsToMtbByte(convNsToPs(timeNs))
|
|
|
|
}
|
|
|
|
|
|
|
|
func convNsToFtbByte(timeNs int) byte {
|
|
|
|
return convPsToFtbByte(convNsToPs(timeNs))
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTAAMin(memAttribs *memAttributes) byte {
|
|
|
|
return convPsToMtbByte(memAttribs.TAAMinPs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTAAMinFineOffset(memAttribs *memAttributes) byte {
|
|
|
|
return convPsToFtbByte(memAttribs.TAAMinPs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRCDMin(memAttribs *memAttributes) byte {
|
|
|
|
return convNsToMtbByte(memAttribs.TRCDMinNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRCDMinFineOffset(memAttribs *memAttributes) byte {
|
|
|
|
return convNsToFtbByte(memAttribs.TRCDMinNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRPABMin(memAttribs *memAttributes) byte {
|
|
|
|
return convNsToMtbByte(memAttribs.TRPABMinNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRPABMinFineOffset(memAttribs *memAttributes) byte {
|
|
|
|
return convNsToFtbByte(memAttribs.TRPABMinNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRPPBMin(memAttribs *memAttributes) byte {
|
|
|
|
return convNsToMtbByte(memAttribs.TRPPBMinNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRPPBMinFineOffset(memAttribs *memAttributes) byte {
|
|
|
|
return convNsToFtbByte(memAttribs.TRPPBMinNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRFCABMinMsb(memAttribs *memAttributes) byte {
|
|
|
|
return byte((convNsToMtb(memAttribs.TRFCABNs) >> 8) & 0xff)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRFCABMinLsb(memAttribs *memAttributes) byte {
|
|
|
|
return byte(convNsToMtb(memAttribs.TRFCABNs) & 0xff)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRFCPBMinMsb(memAttribs *memAttributes) byte {
|
|
|
|
return byte((convNsToMtb(memAttribs.TRFCPBNs) >> 8) & 0xff)
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeTRFCPBMinLsb(memAttribs *memAttributes) byte {
|
|
|
|
return byte(convNsToMtb(memAttribs.TRFCPBNs) & 0xff)
|
|
|
|
}
|
|
|
|
|
|
|
|
type SPDAttribFunc func (*memAttributes) byte
|
|
|
|
|
|
|
|
type SPDAttribTableEntry struct {
|
|
|
|
constVal byte
|
|
|
|
getVal SPDAttribFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
/* SPD Byte Index */
|
|
|
|
SPDIndexSize = 0
|
|
|
|
SPDIndexRevision = 1
|
|
|
|
SPDIndexMemoryType = 2
|
|
|
|
SPDIndexModuleType = 3
|
|
|
|
SPDIndexDensityBanks = 4
|
|
|
|
SPDIndexAddressing = 5
|
|
|
|
SPDIndexPackageType = 6
|
|
|
|
SPDIndexOptionalFeatures = 7
|
|
|
|
SPDIndexModuleOrganization = 12
|
|
|
|
SPDIndexBusWidth = 13
|
|
|
|
SPDIndexTimebases = 17
|
|
|
|
SPDIndexTCKMin = 18
|
|
|
|
SPDIndexTCKMax = 19
|
|
|
|
SPDIndexCASFirstByte = 20
|
|
|
|
SPDIndexCASSecondByte = 21
|
|
|
|
SPDIndexCASThirdByte = 22
|
|
|
|
SPDIndexCASFourthByte = 23
|
|
|
|
SPDIndexTAAMin = 24
|
|
|
|
SPDIndexReadWriteLatency = 25
|
|
|
|
SPDIndexTRCDMin = 26
|
|
|
|
SPDIndexTRPABMin = 27
|
|
|
|
SPDIndexTRPPBMin = 28
|
|
|
|
SPDIndexTRFCABMinLSB = 29
|
|
|
|
SPDIndexTRFCABMinMSB = 30
|
|
|
|
SPDIndexTRFCPBMinLSB = 31
|
|
|
|
SPDIndexTRFCPBMinMSB = 32
|
|
|
|
SPDIndexTRPPBMinFineOffset = 120
|
|
|
|
SPDIndexTRPABMinFineOffset = 121
|
|
|
|
SPDIndexTRCDMinFineOffset = 122
|
|
|
|
SPDIndexTAAMinFineOffset = 123
|
|
|
|
SPDIndexTCKMaxFineOffset = 124
|
|
|
|
SPDIndexTCKMinFineOffset = 125
|
2020-06-03 07:45:52 +02:00
|
|
|
SPDIndexManufacturerPartNumberStartByte = 329
|
|
|
|
SPDIndexManufacturerPartNumberEndByte = 348
|
2020-05-21 06:37:51 +02:00
|
|
|
|
|
|
|
/* SPD Byte Value */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* From JEDEC spec:
|
|
|
|
* 6:4 (Bytes total) = 2 (512 bytes)
|
|
|
|
* 3:0 (Bytes used) = 3 (384 bytes)
|
|
|
|
* Set to 0x23 for LPDDR4x.
|
|
|
|
*/
|
|
|
|
SPDValueSize = 0x23
|
|
|
|
|
|
|
|
/*
|
|
|
|
* From JEDEC spec: Revision 1.1
|
|
|
|
* Set to 0x11.
|
|
|
|
*/
|
|
|
|
SPDValueRevision = 0x11
|
|
|
|
|
|
|
|
/* LPDDR4x memory type = 0x11 */
|
|
|
|
SPDValueMemoryType = 0x11
|
|
|
|
|
|
|
|
/*
|
|
|
|
* From JEDEC spec:
|
|
|
|
* 7:7 (Hybrid) = 0 (Not hybrid)
|
|
|
|
* 6:4 (Hybrid media) = 000 (Not hybrid)
|
|
|
|
* 3:0 (Base Module Type) = 1110 (Non-DIMM solution)
|
|
|
|
*
|
|
|
|
* This is dependent on hardware design. LPDDR4x only has memory down solution.
|
|
|
|
* Hence this is not hybrid non-DIMM solution.
|
|
|
|
* Set to 0x0E.
|
|
|
|
*/
|
|
|
|
SPDValueModuleType = 0x0e
|
|
|
|
|
|
|
|
/*
|
|
|
|
* From JEDEC spec:
|
|
|
|
* 5:4 (Maximum Activate Window) = 00 (8192 * tREFI)
|
|
|
|
* 3:0 (Maximum Activate Count) = 1000 (Unlimited MAC)
|
|
|
|
*
|
|
|
|
* Needs to come from datasheet, but most parts seem to support unlimited MAC.
|
|
|
|
* MR#24 OP3
|
|
|
|
*/
|
|
|
|
SPDValueOptionalFeatures = 0x08
|
|
|
|
|
|
|
|
/*
|
|
|
|
* From JEDEC spec:
|
|
|
|
* 3:2 (MTB) = 00 (0.125ns)
|
|
|
|
* 1:0 (FTB) = 00 (1ps)
|
|
|
|
* Set to 0x00.
|
|
|
|
*/
|
|
|
|
SPDValueTimebases = 0x00
|
|
|
|
|
|
|
|
/* CAS fourth byte: All bits are reserved */
|
|
|
|
SPDValueCASFourthByte = 0x00
|
|
|
|
|
|
|
|
/* Write Latency Set A and Read Latency DBI-RD disabled. */
|
|
|
|
SPDValueReadWriteLatency = 0x00
|
2020-06-03 07:45:52 +02:00
|
|
|
|
|
|
|
/* As per JEDEC spec, unused digits of manufacturer part number are left as blank. */
|
|
|
|
SPDValueManufacturerPartNumberBlank = 0x20
|
2020-05-21 06:37:51 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var SPDAttribTable = map[int]SPDAttribTableEntry {
|
|
|
|
SPDIndexSize: { constVal: SPDValueSize },
|
|
|
|
SPDIndexRevision: { constVal: SPDValueRevision },
|
|
|
|
SPDIndexMemoryType: { constVal: SPDValueMemoryType },
|
|
|
|
SPDIndexModuleType: { constVal: SPDValueModuleType },
|
|
|
|
SPDIndexDensityBanks: { getVal: encodeDensityBanks },
|
|
|
|
SPDIndexAddressing: { getVal: encodeSdramAddressing },
|
|
|
|
SPDIndexPackageType: { getVal: encodePackageType },
|
|
|
|
SPDIndexOptionalFeatures: { constVal: SPDValueOptionalFeatures },
|
|
|
|
SPDIndexModuleOrganization: { getVal: encodeModuleOrganization },
|
|
|
|
SPDIndexBusWidth: { getVal: encodeBusWidth },
|
|
|
|
SPDIndexTimebases: { constVal: SPDValueTimebases },
|
|
|
|
SPDIndexTCKMin: { getVal: encodeTCKMin },
|
|
|
|
SPDIndexTCKMax: { getVal: encodeTCKMax },
|
|
|
|
SPDIndexTCKMaxFineOffset: { getVal: encodeTCKMaxFineOffset },
|
|
|
|
SPDIndexTCKMinFineOffset: { getVal: encodeTCKMinFineOffset },
|
|
|
|
SPDIndexCASFirstByte: { getVal: encodeCASFirstByte },
|
|
|
|
SPDIndexCASSecondByte: { getVal: encodeCASSecondByte },
|
|
|
|
SPDIndexCASThirdByte: { getVal: encodeCASThirdByte },
|
|
|
|
SPDIndexCASFourthByte: { constVal: SPDValueCASFourthByte },
|
|
|
|
SPDIndexTAAMin: { getVal: encodeTAAMin },
|
|
|
|
SPDIndexTAAMinFineOffset: { getVal: encodeTAAMinFineOffset },
|
|
|
|
SPDIndexReadWriteLatency: { constVal: SPDValueReadWriteLatency },
|
|
|
|
SPDIndexTRCDMin: { getVal: encodeTRCDMin },
|
|
|
|
SPDIndexTRCDMinFineOffset: { getVal: encodeTRCDMinFineOffset },
|
|
|
|
SPDIndexTRPABMin: { getVal: encodeTRPABMin },
|
|
|
|
SPDIndexTRPABMinFineOffset: { getVal: encodeTRPABMinFineOffset },
|
|
|
|
SPDIndexTRPPBMin: { getVal: encodeTRPPBMin },
|
|
|
|
SPDIndexTRPPBMinFineOffset: { getVal: encodeTRPPBMinFineOffset },
|
|
|
|
SPDIndexTRFCABMinLSB: { getVal: encodeTRFCABMinLsb },
|
|
|
|
SPDIndexTRFCABMinMSB: { getVal: encodeTRFCABMinMsb },
|
|
|
|
SPDIndexTRFCPBMinLSB: { getVal: encodeTRFCPBMinLsb },
|
|
|
|
SPDIndexTRFCPBMinMSB: { getVal: encodeTRFCPBMinMsb },
|
|
|
|
}
|
|
|
|
|
|
|
|
type memParts struct {
|
|
|
|
MemParts []memPart `json:"parts"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type memPart struct {
|
|
|
|
Name string
|
|
|
|
Attribs memAttributes
|
|
|
|
SPDFileName string
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeSPDManifest(memParts *memParts, SPDDirName string) error {
|
|
|
|
var s string
|
|
|
|
|
|
|
|
fmt.Printf("Generating SPD Manifest with following entries:\n")
|
|
|
|
|
|
|
|
for i := 0; i < len(memParts.MemParts); i++ {
|
|
|
|
fmt.Printf("%-40s %s\n", memParts.MemParts[i].Name, memParts.MemParts[i].SPDFileName)
|
|
|
|
s += fmt.Sprintf("%s,%s\n", memParts.MemParts[i].Name, memParts.MemParts[i].SPDFileName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.WriteFile(filepath.Join(SPDDirName, SPDManifestFileName), []byte(s), 0644)
|
|
|
|
}
|
|
|
|
|
2020-06-03 07:45:52 +02:00
|
|
|
func isManufacturerPartNumberByte(index int) bool {
|
|
|
|
if index >= SPDIndexManufacturerPartNumberStartByte && index <= SPDIndexManufacturerPartNumberEndByte {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-05-21 06:37:51 +02:00
|
|
|
func getSPDByte(index int, memAttribs *memAttributes) byte {
|
|
|
|
e, ok := SPDAttribTable[index]
|
|
|
|
if ok == false {
|
2020-06-03 07:45:52 +02:00
|
|
|
if isManufacturerPartNumberByte(index) {
|
|
|
|
return SPDValueManufacturerPartNumberBlank
|
|
|
|
}
|
2020-05-21 06:37:51 +02:00
|
|
|
return 0x00
|
|
|
|
}
|
|
|
|
|
|
|
|
if e.getVal != nil {
|
|
|
|
return e.getVal(memAttribs)
|
|
|
|
}
|
|
|
|
|
|
|
|
return e.constVal
|
|
|
|
}
|
|
|
|
|
2020-08-28 00:59:19 +02:00
|
|
|
func createSPD(memAttribs *memAttributes) bytes.Buffer {
|
|
|
|
var spd bytes.Buffer
|
2020-05-21 06:37:51 +02:00
|
|
|
|
|
|
|
for i := 0; i < 512; i++ {
|
2020-08-28 00:59:19 +02:00
|
|
|
spd.WriteByte(getSPDByte(i, memAttribs))
|
2020-05-21 06:37:51 +02:00
|
|
|
}
|
|
|
|
|
2020-08-28 00:59:19 +02:00
|
|
|
return spd
|
2020-05-21 06:37:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func dedupeMemoryPart(dedupedParts []*memPart, memPart *memPart) bool {
|
|
|
|
for i := 0; i < len(dedupedParts); i++ {
|
|
|
|
if reflect.DeepEqual(dedupedParts[i].Attribs, memPart.Attribs) {
|
|
|
|
memPart.SPDFileName = dedupedParts[i].SPDFileName
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateSPD(memPart *memPart, SPDId int, SPDDirName string) {
|
2020-08-28 00:59:19 +02:00
|
|
|
spd := createSPD(&memPart.Attribs)
|
|
|
|
memPart.SPDFileName = fmt.Sprintf("lp4x-spd-%d.bin", SPDId)
|
|
|
|
ioutil.WriteFile(filepath.Join(SPDDirName, memPart.SPDFileName), spd.Bytes(), 0644)
|
2020-05-21 06:37:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func readMemoryParts(memParts *memParts, memPartsFileName string) error {
|
|
|
|
databytes, err := ioutil.ReadFile(memPartsFileName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Unmarshal(databytes, memParts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateDensityx8Channel(densityPerChannelGb int) error {
|
|
|
|
if _, ok := densityGbx8ChannelToRowColumnEncoding[densityPerChannelGb]; ok == false {
|
|
|
|
return fmt.Errorf("Incorrect x8 density: ", densityPerChannelGb, "Gb")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateDensityx16Channel(densityPerChannelGb int) error {
|
|
|
|
if _, ok := densityGbx16ChannelToRowColumnEncoding[densityPerChannelGb]; ok == false {
|
|
|
|
return fmt.Errorf("Incorrect x16 density: ", densityPerChannelGb, "Gb")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateDensity(memAttribs *memAttributes) error {
|
|
|
|
if memAttribs.BitWidthPerChannel == 8 {
|
|
|
|
return validateDensityx8Channel(memAttribs.DensityPerChannelGb)
|
|
|
|
} else if memAttribs.BitWidthPerChannel == 16 {
|
|
|
|
return validateDensityx16Channel(memAttribs.DensityPerChannelGb)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("No density table for this bit width: ", memAttribs.BitWidthPerChannel)
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateBanks(banks int) error {
|
|
|
|
if banks != 4 && banks != 8 {
|
|
|
|
return fmt.Errorf("Incorrect banks: ", banks)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateChannels(channels int) error {
|
|
|
|
if channels != 1 && channels != 2 && channels != 4 {
|
|
|
|
return fmt.Errorf("Incorrect channels per die: ", channels)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateDataWidth(width int) error {
|
|
|
|
if width != 8 && width != 16 {
|
|
|
|
return fmt.Errorf("Incorrect bit width: ", width)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateRanks(ranks int) error {
|
|
|
|
if ranks != 1 && ranks != 2 {
|
|
|
|
return fmt.Errorf("Incorrect ranks: ", ranks)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateSpeed(speed int) error {
|
|
|
|
if _, ok := speedMbpsToSPDEncoding[speed]; ok == false {
|
|
|
|
return fmt.Errorf("Incorrect speed: ", speed, " Mbps")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateMemoryParts(memParts *memParts) error {
|
|
|
|
for i := 0; i < len(memParts.MemParts); i++ {
|
|
|
|
if err := validateBanks(memParts.MemParts[i].Attribs.Banks); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := validateChannels(memParts.MemParts[i].Attribs.ChannelsPerDie); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := validateDataWidth(memParts.MemParts[i].Attribs.BitWidthPerChannel); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := validateDensity(&memParts.MemParts[i].Attribs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := validateRanks(memParts.MemParts[i].Attribs.RanksPerChannel); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := validateSpeed(memParts.MemParts[i].Attribs.SpeedMbps); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeLatencies(latency int, memAttribs *memAttributes) error {
|
|
|
|
switch latency {
|
|
|
|
case 6:
|
|
|
|
memAttribs.CASFirstByte |= CAS6
|
|
|
|
case 10:
|
|
|
|
memAttribs.CASFirstByte |= CAS10
|
|
|
|
case 14:
|
|
|
|
memAttribs.CASFirstByte |= CAS14
|
|
|
|
case 16:
|
|
|
|
memAttribs.CASSecondByte |= CAS16
|
|
|
|
case 20:
|
|
|
|
memAttribs.CASSecondByte |= CAS20
|
|
|
|
case 22:
|
|
|
|
memAttribs.CASSecondByte |= CAS22
|
|
|
|
case 24:
|
|
|
|
memAttribs.CASSecondByte |= CAS24
|
|
|
|
case 26:
|
|
|
|
memAttribs.CASSecondByte |= CAS26
|
|
|
|
case 28:
|
|
|
|
memAttribs.CASSecondByte |= CAS28
|
|
|
|
case 32:
|
|
|
|
memAttribs.CASThirdByte |= CAS32
|
|
|
|
case 36:
|
|
|
|
memAttribs.CASThirdByte |= CAS36
|
|
|
|
case 40:
|
|
|
|
memAttribs.CASThirdByte |= CAS40
|
|
|
|
default:
|
|
|
|
fmt.Errorf("Incorrect CAS Latency: ", latency)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTCK(memAttribs *memAttributes) {
|
|
|
|
if memAttribs.TCKMinPs == 0 {
|
|
|
|
memAttribs.TCKMinPs = speedMbpsToSPDEncoding[memAttribs.SpeedMbps].TCKMinPs
|
|
|
|
}
|
|
|
|
if memAttribs.TCKMaxPs == 0 {
|
|
|
|
memAttribs.TCKMaxPs = speedMbpsToSPDEncoding[memAttribs.SpeedMbps].TCKMaxPs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCASLatencies(memAttribs *memAttributes) string {
|
|
|
|
if memAttribs.BitWidthPerChannel == 16 {
|
|
|
|
return speedMbpsToSPDEncoding[memAttribs.SpeedMbps].CASLatenciesx16Channel
|
|
|
|
} else if memAttribs.BitWidthPerChannel == 8 {
|
|
|
|
return speedMbpsToSPDEncoding[memAttribs.SpeedMbps].CASLatenciesx8Channel
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateCAS(memAttribs *memAttributes) error {
|
|
|
|
if len(memAttribs.CASLatencies) == 0 {
|
|
|
|
memAttribs.CASLatencies = getCASLatencies(memAttribs)
|
|
|
|
}
|
|
|
|
|
|
|
|
latencies := strings.Fields(memAttribs.CASLatencies)
|
|
|
|
for i := 0; i < len(latencies); i++ {
|
|
|
|
latency,err := strconv.Atoi(latencies[i])
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to convert latency ", latencies[i])
|
|
|
|
}
|
|
|
|
if err := encodeLatencies(latency, memAttribs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMinCAS(memAttribs *memAttributes) (int, error) {
|
|
|
|
if (memAttribs.CASThirdByte & CAS40) != 0 {
|
|
|
|
return 40, nil
|
|
|
|
}
|
|
|
|
if (memAttribs.CASThirdByte & CAS36) != 0 {
|
|
|
|
return 36, nil
|
|
|
|
}
|
|
|
|
if (memAttribs.CASThirdByte & CAS32) != 0 {
|
|
|
|
return 32, nil
|
|
|
|
}
|
|
|
|
if (memAttribs.CASSecondByte & CAS28) != 0 {
|
|
|
|
return 28, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0, fmt.Errorf("Unexpected min CAS")
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTAAMin(memAttribs *memAttributes) error {
|
|
|
|
if memAttribs.TAAMinPs == 0 {
|
|
|
|
minCAS, err := getMinCAS(memAttribs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
memAttribs.TAAMinPs = memAttribs.TCKMinPs * minCAS
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTRFCAB(memAttribs *memAttributes) {
|
|
|
|
if memAttribs.TRFCABNs == 0 {
|
|
|
|
memAttribs.TRFCABNs = densityGbPhysicalChannelToRefreshEncoding[memAttribs.DensityPerChannelGb].TRFCABNs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTRFCPB(memAttribs *memAttributes) {
|
|
|
|
if memAttribs.TRFCPBNs == 0 {
|
|
|
|
memAttribs.TRFCPBNs = densityGbPhysicalChannelToRefreshEncoding[memAttribs.DensityPerChannelGb].TRFCPBNs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTRCD(memAttribs *memAttributes) {
|
|
|
|
if memAttribs.TRCDMinNs == 0 {
|
|
|
|
/* JEDEC spec says max of 18ns */
|
|
|
|
memAttribs.TRCDMinNs = 18
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTRPAB(memAttribs *memAttributes) {
|
|
|
|
if memAttribs.TRPABMinNs == 0 {
|
|
|
|
/* JEDEC spec says max of 21ns */
|
|
|
|
memAttribs.TRPABMinNs = 21
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTRPPB(memAttribs *memAttributes) {
|
|
|
|
if memAttribs.TRPPBMinNs == 0 {
|
|
|
|
/* JEDEC spec says max of 18ns */
|
|
|
|
memAttribs.TRPPBMinNs = 18
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalizeMemoryAttributes(memAttribs *memAttributes) {
|
|
|
|
if currPlatform == PlatformTGL {
|
|
|
|
/*
|
|
|
|
* TGL does not really use physical organization of dies per package when
|
|
|
|
* generating the SPD. So, set it to 0 here so that deduplication ignores
|
|
|
|
* that field.
|
|
|
|
*/
|
|
|
|
memAttribs.DiesPerPackage = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateMemoryAttributes(memAttribs *memAttributes) error {
|
|
|
|
updateTCK(memAttribs)
|
|
|
|
if err := updateCAS(memAttribs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := updateTAAMin(memAttribs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
updateTRFCAB(memAttribs)
|
|
|
|
updateTRFCPB(memAttribs)
|
|
|
|
updateTRCD(memAttribs)
|
|
|
|
updateTRPAB(memAttribs)
|
|
|
|
updateTRPPB(memAttribs)
|
|
|
|
|
|
|
|
normalizeMemoryAttributes(memAttribs)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isPlatformSupported(platform string) error {
|
|
|
|
var ok bool
|
|
|
|
|
|
|
|
currPlatform, ok = platformMap[platform]
|
|
|
|
if ok == false {
|
|
|
|
return fmt.Errorf("Unsupported platform: ", platform)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func usage() {
|
|
|
|
fmt.Printf("\nUsage: %s <spd_dir> <mem_parts_list_json> <platform>\n\n", os.Args[0])
|
|
|
|
fmt.Printf(" where,\n")
|
|
|
|
fmt.Printf(" spd_dir = Directory path containing SPD files and manifest generated by gen_spd.go\n")
|
|
|
|
fmt.Printf(" mem_parts_list_json = JSON File containing list of memory parts and attributes\n")
|
|
|
|
fmt.Printf(" platform = SoC Platform for which the SPDs are being generated\n\n\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
if len(os.Args) != 4 {
|
|
|
|
usage()
|
|
|
|
log.Fatal("Incorrect number of arguments")
|
|
|
|
}
|
|
|
|
|
|
|
|
var memParts memParts
|
|
|
|
var dedupedParts []*memPart
|
|
|
|
|
|
|
|
SPDDir, GlobalMemPartsFile, Platform := os.Args[1], os.Args[2], strings.ToUpper(os.Args[3])
|
|
|
|
|
|
|
|
if err := isPlatformSupported(Platform); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := readMemoryParts(&memParts, GlobalMemPartsFile); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := validateMemoryParts(&memParts); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
SPDId := 1
|
|
|
|
|
|
|
|
for i := 0; i < len(memParts.MemParts); i++ {
|
|
|
|
if err := updateMemoryAttributes(&memParts.MemParts[i].Attribs); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if dedupeMemoryPart(dedupedParts, &memParts.MemParts[i]) == false {
|
|
|
|
generateSPD(&memParts.MemParts[i], SPDId, SPDDir)
|
|
|
|
SPDId++
|
|
|
|
dedupedParts = append(dedupedParts, &memParts.MemParts[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := writeSPDManifest(&memParts, SPDDir); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|