blob: b641711df199b98b7b868b5a2455f1349ce665c5 [file] [log] [blame]
Angel Ponsc74dae92020-04-02 23:48:16 +02001/* SPDX-License-Identifier: GPL-2.0-only */
2/* This file is part of the coreboot project. */
Andrey Petrov3f85edb2019-08-01 14:18:06 -07003
4#include <console/console.h>
5#include <cbmem.h>
6#include <device/device.h>
7#include <device/dram/ddr4.h>
8#include <string.h>
9#include <memory_info.h>
10#include <smbios.h>
11#include <types.h>
12
13typedef enum {
14 BLOCK_0, /* Base Configuration and DRAM Parameters */
15 BLOCK_1,
16 BLOCK_1_L, /* Standard Module Parameters */
17 BLOCK_1_H, /* Hybrid Module Parameters */
18 BLOCK_2,
19 BLOCK_2_L, /* Hybrid Module Extended Function Parameters */
20 BLOCK_2_H, /* Manufacturing Information */
21 BLOCK_3 /* End user programmable */
22} spd_block_type;
23
24typedef struct {
25 spd_block_type type;
26 uint16_t start; /* starting offset from beginning of the spd */
27 uint16_t len; /* size of the block */
28 uint16_t crc_start; /* offset from start of crc bytes, 0 if none */
29} spd_block;
30
31/* 'SPD contents architecture' as per datasheet */
32const spd_block spd_blocks[] = {
33 {.type = BLOCK_0, 0, 128, 126}, {.type = BLOCK_1, 128, 128, 126},
34 {.type = BLOCK_1_L, 128, 64, 0}, {.type = BLOCK_1_H, 192, 64, 0},
35 {.type = BLOCK_2_L, 256, 64, 62}, {.type = BLOCK_2_H, 320, 64, 0},
36 {.type = BLOCK_3, 384, 128, 0} };
37
38static bool verify_block(const spd_block *block, spd_raw_data spd)
39{
40 uint16_t crc, spd_crc;
41
42 spd_crc = (spd[block->start + block->crc_start + 1] << 8)
43 | spd[block->start + block->crc_start];
44 crc = ddr_crc16(&spd[block->start], block->len - 2);
45
46 return spd_crc == crc;
47}
48
49/* Check if given block is 'reserved' for a given module type */
50static bool block_exists(spd_block_type type, u8 dimm_type)
51{
52 bool is_hybrid;
53
54 switch (type) {
55 case BLOCK_0: /* fall-through */
56 case BLOCK_1: /* fall-through */
57 case BLOCK_1_L: /* fall-through */
58 case BLOCK_1_H: /* fall-through */
59 case BLOCK_2_H: /* fall-through */
60 case BLOCK_3: /* fall-through */
61 return true;
62 case BLOCK_2_L:
63 is_hybrid = (dimm_type >> 4) & ((1 << 3) - 1);
64 if (is_hybrid)
65 return true;
66 return false;
67 default: /* fall-through */
68 return false;
69 }
70}
71
72
73/**
74 * \brief Decode the raw SPD data
75 *
76 * Decodes a raw SPD data from a DDR4 DIMM, and organizes it into a
77 * @ref dimm_attr structure. The SPD data must first be read in a contiguous
78 * array, and passed to this function.
79 *
80 * @param dimm pointer to @ref dimm_attr structure where the decoded data is to
81 * be stored
82 * @param spd array of raw data previously read from the SPD.
83 *
84 * @return @ref spd_status enumerator
85 * SPD_STATUS_OK -- decoding was successful
86 * SPD_STATUS_INVALID -- invalid SPD or not a DDR4 SPD
87 * SPD_STATUS_CRC_ERROR -- checksum mismatch
88 */
89int spd_decode_ddr4(dimm_attr *dimm, spd_raw_data spd)
90{
91 u8 reg8;
92 u8 bus_width, sdram_width;
93 u16 cap_per_die_mbit;
94 u16 spd_bytes_total, spd_bytes_used;
95 const uint16_t spd_bytes_used_table[] = {0, 128, 256, 384, 512};
96
97 /* Make sure that the SPD dump is indeed from a DDR4 module */
98 if (spd[2] != SPD_MEMORY_TYPE_DDR4_SDRAM) {
99 printk(BIOS_ERR, "Not a DDR4 SPD!\n");
100 dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED;
101 return SPD_STATUS_INVALID;
102 }
103
Elyes HAOUAS2a0fbe32019-09-24 14:24:45 +0200104 spd_bytes_total = (spd[0] >> 4) & 0x7;
105 spd_bytes_used = spd[0] & 0xf;
Andrey Petrov3f85edb2019-08-01 14:18:06 -0700106
107 if (!spd_bytes_total || !spd_bytes_used) {
108 printk(BIOS_ERR, "SPD failed basic sanity checks\n");
109 return SPD_STATUS_INVALID;
110 }
111
Elyes HAOUAS2a0fbe32019-09-24 14:24:45 +0200112 if (spd_bytes_total >= 3)
113 printk(BIOS_WARNING, "SPD Bytes Total value is reserved\n");
114
Andrey Petrov3f85edb2019-08-01 14:18:06 -0700115 spd_bytes_total = 256 << (spd_bytes_total - 1);
Elyes HAOUAS2a0fbe32019-09-24 14:24:45 +0200116
117 if (spd_bytes_used > 4) {
118 printk(BIOS_ERR, "SPD Bytes Used value is reserved\n");
119 return SPD_STATUS_INVALID;
120 }
121
Andrey Petrov3f85edb2019-08-01 14:18:06 -0700122 spd_bytes_used = spd_bytes_used_table[spd_bytes_used];
123
Elyes HAOUAS2a0fbe32019-09-24 14:24:45 +0200124 if (spd_bytes_used > spd_bytes_total) {
125 printk(BIOS_ERR, "SPD Bytes Used is greater than SPD Bytes Total\n");
126 return SPD_STATUS_INVALID;
127 }
128
Andrey Petrov3f85edb2019-08-01 14:18:06 -0700129 /* Verify CRC of blocks that have them, do not step over 'used' length */
130 for (int i = 0; i < ARRAY_SIZE(spd_blocks); i++) {
131 /* this block is not checksumed */
132 if (spd_blocks[i].crc_start == 0)
133 continue;
134 /* we shouldn't have this block */
135 if (spd_blocks[i].start + spd_blocks[i].len > spd_bytes_used)
136 continue;
137 /* check if block exists in the current schema */
138 if (!block_exists(spd_blocks[i].type, spd[3]))
139 continue;
140 if (!verify_block(&spd_blocks[i], spd)) {
141 printk(BIOS_ERR, "CRC failed for block %d\n", i);
142 return SPD_STATUS_CRC_ERROR;
143 }
144 }
145
146 dimm->dram_type = SPD_MEMORY_TYPE_DDR4_SDRAM;
147 dimm->dimm_type = spd[3] & ((1 << 4) - 1);
148
149 reg8 = spd[13] & ((1 << 4) - 1);
150 dimm->bus_width = reg8;
151 bus_width = 8 << (reg8 & ((1 << 3) - 1));
152
153 reg8 = spd[12] & ((1 << 3) - 1);
154 dimm->sdram_width = reg8;
155 sdram_width = 4 << reg8;
156
157 reg8 = spd[4] & ((1 << 4) - 1);
158 dimm->cap_per_die_mbit = reg8;
159 cap_per_die_mbit = (1 << reg8) * 256;
160
161 reg8 = (spd[12] >> 3) & ((1 << 3) - 1);
162 dimm->ranks = reg8 + 1;
163
164 if (!bus_width || !sdram_width) {
165 printk(BIOS_ERR, "SPD information is invalid");
166 dimm->size_mb = 0;
167 return SPD_STATUS_INVALID;
168 }
169
170 /* seems to be only one, in mV */
171 dimm->vdd_voltage = 1200;
172
173 /* calculate size */
174 dimm->size_mb = cap_per_die_mbit / 8 * bus_width / sdram_width * dimm->ranks;
175
176 /* make sure we have the manufacturing information block */
177 if (spd_bytes_used > 320) {
178 dimm->manufacturer_id = (spd[351] << 8) | spd[350];
179 memcpy(dimm->part_number, &spd[329], SPD_DDR4_PART_LEN);
180 dimm->part_number[SPD_DDR4_PART_LEN] = 0;
181 memcpy(dimm->serial_number, &spd[325], sizeof(dimm->serial_number));
182 }
183 return SPD_STATUS_OK;
184}
185
186enum cb_err spd_add_smbios17_ddr4(const u8 channel, const u8 slot, const u16 selected_freq,
187 const dimm_attr *info)
188{
189 struct memory_info *mem_info;
190 struct dimm_info *dimm;
191
192 /*
193 * Allocate CBMEM area for DIMM information used to populate SMBIOS
194 * table 17
195 */
196 mem_info = cbmem_find(CBMEM_ID_MEMINFO);
197 if (!mem_info) {
198 mem_info = cbmem_add(CBMEM_ID_MEMINFO, sizeof(*mem_info));
199
Julius Werner540a9802019-12-09 13:03:29 -0800200 printk(BIOS_DEBUG, "CBMEM entry for DIMM info: %p\n", mem_info);
Andrey Petrov3f85edb2019-08-01 14:18:06 -0700201 if (!mem_info)
202 return CB_ERR;
203
204 memset(mem_info, 0, sizeof(*mem_info));
205 }
206
207 if (mem_info->dimm_cnt >= ARRAY_SIZE(mem_info->dimm)) {
208 printk(BIOS_WARNING, "BUG: Too many DIMM infos for %s.\n", __func__);
209 return CB_ERR;
210 }
211
212 dimm = &mem_info->dimm[mem_info->dimm_cnt];
213 if (info->size_mb) {
214 dimm->ddr_type = MEMORY_TYPE_DDR4;
215 dimm->ddr_frequency = selected_freq;
216 dimm->dimm_size = info->size_mb;
217 dimm->channel_num = channel;
218 dimm->rank_per_dimm = info->ranks;
219 dimm->dimm_num = slot;
220 memcpy(dimm->module_part_number, info->part_number, SPD_DDR4_PART_LEN);
221 dimm->mod_id = info->manufacturer_id;
222
223 switch (info->dimm_type) {
224 case SPD_DIMM_TYPE_SO_DIMM:
225 dimm->mod_type = SPD_SODIMM;
226 break;
227 case SPD_DIMM_TYPE_72B_SO_RDIMM:
228 dimm->mod_type = SPD_72B_SO_RDIMM;
229 break;
230 case SPD_DIMM_TYPE_UDIMM:
231 dimm->mod_type = SPD_UDIMM;
232 break;
233 case SPD_DIMM_TYPE_RDIMM:
234 dimm->mod_type = SPD_RDIMM;
235 break;
236 default:
237 dimm->mod_type = SPD_UNDEFINED;
238 break;
239 }
240
241 dimm->bus_width = info->bus_width;
242 memcpy(dimm->serial, info->serial_number,
243 MIN(sizeof(dimm->serial), sizeof(info->serial_number)));
244
245 dimm->vdd_voltage = info->vdd_voltage;
246 mem_info->dimm_cnt++;
247 }
248
249 return CB_SUCCESS;
250}