8
8
#include "mbr.h"
9
9
#include "gpt.h"
10
10
11
- #define BANKS 8
12
- #define BLOCKS_PER_BANK 4096
13
- #define PAGES_PER_BANK 524288
14
- #define BYTES_PER_PAGE 2048
15
- #define PAGES_PER_SUBLOCK 1024
16
- #define PAGES_PER_BLOCK 128
11
+ #define BYTES_PER_PAGE 4096
17
12
#define BYTES_PER_SPARE 64
18
- #define FIL_ID 0x43303032
13
+ #define PAGES_PER_BLOCK 128
14
+ #define VFL_CTX_PHYSICAL_PAGE PAGES_PER_BLOCK * 1
15
+ #define PAGES_PER_SUBLOCK 1024
16
+ #define CS_TOTAL 4
17
+
18
+ #define NAND_SIG_PAGE 524160
19
+ #define BBT_PAGE 524161
20
+
19
21
#define FTL_CTX_VBLK_IND 0 // virtual block index of the FTL Context
20
22
21
23
#define NUM_PARTITIONS 1
@@ -64,22 +66,65 @@ uint32_t crc32(const uint8_t *buf, int len)
64
66
return update_crc32 (0xffffffffL , buf , len ) ^ 0xffffffffL ;
65
67
}
66
68
67
- void get_physical_address (uint32_t vpn , uint32_t * bank , uint32_t * pn ) {
68
- * bank = vpn % BANKS ;
69
- uint32_t pbi = vpn / PAGES_PER_SUBLOCK ;
70
- uint32_t pib = (vpn / BANKS ) % PAGES_PER_BLOCK ;
71
- * pn = pbi * PAGES_PER_BLOCK + pib ;
69
+ uint8_t * get_valid_ftl_spare () {
70
+ uint32_t * spare = (uint32_t * )calloc (BYTES_PER_SPARE , sizeof (char ));
71
+ spare [2 ] = 0x00FF00FF ;
72
+ return (uint8_t * )spare ;
73
+ }
74
+
75
+ void _Helper_ConvertP2C_OneBitReorder (uint32_t dwBank , uint32_t dwPpn , uint32_t * pdwCE , uint32_t * pdwCpn , uint32_t dwReorderMask )
76
+ {
77
+ const uint32_t dwBelowReorderMask = dwReorderMask - 1 ; // Assumption: dwReorderMask is a power of 2!
78
+ const uint32_t dwAboveReorderMask = ~dwBelowReorderMask ;
79
+
80
+ // insert reorder bit back in correct position of "chip" page number by extracting from MSB of "physical" bank number
81
+ * pdwCpn = ((dwPpn & dwBelowReorderMask ) |
82
+ (((dwBank / CS_TOTAL ) & 0x1 ) ? dwReorderMask : 0 ) |
83
+ ((dwPpn & dwAboveReorderMask ) << 1 ));
84
+
85
+ // strip reorder bit from MSB of "physical" bank number to produce "chip" CE
86
+ * pdwCE = dwBank % CS_TOTAL ;
87
+ }
88
+
89
+ void _pfnConvertP2C_TwoPlaneLSB (uint32_t dwBank , uint32_t dwPpn , uint32_t * pdwCE , uint32_t * pdwCpn )
90
+ {
91
+ _Helper_ConvertP2C_OneBitReorder (dwBank , dwPpn , pdwCE , pdwCpn , PAGES_PER_BLOCK );
92
+ }
93
+
94
+ void _ConvertT2P_Default (uint16_t wBank , uint16_t wTbn , uint16_t * pwPbn )
95
+ {
96
+ uint32_t dwCpn , dwCS ;
97
+ _pfnConvertP2C_TwoPlaneLSB ((uint32_t )wBank , (uint32_t )(wTbn * PAGES_PER_BLOCK ), & dwCS , & dwCpn );
98
+ * pwPbn = (uint16_t )(dwCpn / PAGES_PER_BLOCK );
72
99
}
73
100
74
- void write_page (uint8_t * page , uint8_t * spare , int bank , int page_index ) {
101
+ void _Vpn2Ppn (uint32_t dwVpn , uint16_t * pwCS , uint32_t * pdwPpn ) {
102
+ uint16_t wPbn , wPOffset ;
103
+ uint16_t wBank = dwVpn % WMR_NUM_OF_BANKS ;
104
+ uint16_t wVbn = dwVpn / PAGES_PER_SUBLOCK ;
105
+ _ConvertT2P_Default ((wBank & 0xffff ), wVbn , & wPbn );
106
+ * pwCS = wBank % CS_TOTAL ;
107
+ wPOffset = (uint16_t )((dwVpn % PAGES_PER_SUBLOCK ) / WMR_NUM_OF_BANKS );
108
+ * pdwPpn = wPbn * PAGES_PER_BLOCK + wPOffset ;
109
+ }
110
+
111
+ void _Lpn2Ppn (uint32_t dwLpn , uint16_t * pwCS , uint32_t * pdwPpn ) {
112
+ uint32_t dwLbn = dwLpn / PAGES_PER_SUBLOCK ;
113
+ uint16_t wLPOffset = (uint16_t )(dwLpn - dwLbn * PAGES_PER_SUBLOCK );
114
+ uint32_t dwVbn = dwLbn + 1 ;
115
+ uint32_t dwVpn = dwVbn * PAGES_PER_SUBLOCK + wLPOffset ;
116
+ _Vpn2Ppn (dwVpn , pwCS , pdwPpn );
117
+ }
118
+
119
+ void write_page (uint8_t * page , uint8_t * spare , int cs , int page_index ) {
75
120
char filename [100 ];
76
- sprintf (filename , "nand/bank %d" , bank );
121
+ sprintf (filename , "nand/cs %d" , cs );
77
122
struct stat st = {0 };
78
123
if (stat (filename , & st ) == -1 ) {
79
124
mkdir (filename , 0700 );
80
125
}
81
126
82
- sprintf (filename , "nand/bank %d/%d.page" , bank , page_index );
127
+ sprintf (filename , "nand/cs %d/%d.page" , cs , page_index );
83
128
FILE * f = fopen (filename , "wb" );
84
129
85
130
if (!page ) {
@@ -95,56 +140,86 @@ void write_page(uint8_t *page, uint8_t *spare, int bank, int page_index) {
95
140
fclose (f );
96
141
}
97
142
98
- void write_fil_id () {
99
- // set the FIL ID on the very first byte of the first bank
100
- uint8_t * page0_b0 = (uint8_t * )calloc (BYTES_PER_PAGE , sizeof (char ));
101
- uint8_t * spare_page0_b0 = (uint8_t * )calloc (BYTES_PER_SPARE , sizeof (char ));
102
- ((uint32_t * )page0_b0 )[0 ] = FIL_ID ;
103
- write_page (page0_b0 , spare_page0_b0 , 0 , 0 );
104
- }
105
-
106
143
void write_bbts () {
107
- // create the bad block table on the first physical page of the last physical block
108
- for (int bank = 0 ; bank < BANKS ; bank ++ ) {
144
+ // create the bad block table on each CS
145
+ for (int cs = 0 ; cs < CS_TOTAL ; cs ++ ) {
109
146
uint8_t * page = calloc (BYTES_PER_PAGE , sizeof (char ));
110
147
memcpy (page , "DEVICEINFOBBT\0\0\0" , 16 );
111
- write_page (page , NULL , bank , PAGES_PER_BANK - PAGES_PER_BLOCK );
148
+
149
+ // set all bits in this page to 1, to indicate that all blocks are of good health.
150
+ for (int i = 16 ; i < BYTES_PER_PAGE ; i ++ ) {
151
+ page [i ] = 0xFF ;
152
+ }
153
+
154
+ write_page (page , NULL , cs , BBT_PAGE );
112
155
}
113
156
}
114
157
158
+ void write_nand_sig_page () {
159
+ // write the NAND signature page
160
+ uint8_t * page = calloc (0x100 , sizeof (uint8_t ));
161
+ char * magic = "NANDDRIVERSIGN" ;
162
+ memcpy (page , magic , strlen (magic ));
163
+ page [0x34 ] = 0x4 ; // length of the info
164
+
165
+ // signature (0x43313131)
166
+ page [0x38 ] = 0x31 ;
167
+ page [0x39 ] = 0x31 ;
168
+ page [0x3A ] = 0x31 ;
169
+ page [0x3B ] = 0x43 ;
170
+ write_page (page , NULL , 0 , NAND_SIG_PAGE );
171
+ }
172
+
115
173
void write_vfl_context () {
116
- for (int bank = 0 ; bank < BANKS ; bank ++ ) {
174
+ for (int wCSIdx = 0 ; wCSIdx < CS_TOTAL ; wCSIdx ++ ) {
117
175
// initialize VFL context block on physical block 35, page 0
118
176
VFLMeta * vfl_meta = (VFLMeta * )calloc (sizeof (VFLMeta ), sizeof (char ));
119
- vfl_meta -> stVFLCxt .awInfoBlk [0 ] = 35 ;
120
-
121
- // write the bad mark table
122
- for (int i = 0 ; i < VFL_BAD_MARK_INFO_TABLE_SIZE ; i ++ ) {
123
- vfl_meta -> stVFLCxt .aBadMark [i ] = 0xff ;
177
+ vfl_meta -> dwVersion = VFL_META_VERSION ;
178
+
179
+ VFLCxt * pVFLCxt = & vfl_meta -> stVFLCxt ;
180
+
181
+ // we set the number of VFL/FTL user blocks to 2048 which doesn't give any room for the BBT table. This is fine as we do not have bad blocks.
182
+ pVFLCxt -> wNumOfVFLSuBlk = 2048 ;
183
+ pVFLCxt -> wNumOfFTLSuBlk = 2048 ;
184
+
185
+ pVFLCxt -> abVSFormtType = VFL_VENDOR_SPECIFIC_TYPE ;
186
+ pVFLCxt -> dwGlobalCxtAge = wCSIdx ;
187
+ pVFLCxt -> wCxtLocation = 1 ; // the VFL context is located in the first physical block
188
+ for (int wIdx = 0 ; wIdx < FTL_CXT_SECTION_SIZE ; wIdx ++ )
189
+ {
190
+ pVFLCxt -> awFTLCxtVbn [wIdx ] = (uint16_t )(wIdx );
191
+ }
192
+ pVFLCxt -> wNumOfInitBadBlk = 0 ;
193
+
194
+ for (int wIdx = 0 , wPbn = VFL_FIRST_BLK_TO_SEARCH_CXT ; wIdx < VFL_INFO_SECTION_SIZE && wPbn < VFL_LAST_BLK_TO_SEARCH_CXT ; wPbn ++ )
195
+ {
196
+ pVFLCxt -> awInfoBlk [wIdx ++ ] = wPbn ;
197
+ }
198
+
199
+ // update the bad block map
200
+ for (int i = 0 ; i < WMR_MAX_RESERVED_SIZE ; i ++ ) {
201
+ pVFLCxt -> awBadMapTable [i ] = VFL_BAD_MAP_TABLE_AVAILABLE_MARK ;
124
202
}
125
203
126
204
VFLSpare * vfl_ctx_spare = (VFLSpare * )calloc (BYTES_PER_SPARE , sizeof (char ));
127
205
vfl_ctx_spare -> dwCxtAge = 1 ;
128
206
vfl_ctx_spare -> bSpareType = VFL_CTX_SPARE_TYPE ;
129
207
130
- // indicate the location of the FTL CTX block
131
- vfl_meta -> stVFLCxt .aFTLCxtVbn [0 ] = FTL_CTX_VBLK_IND ;
132
- vfl_meta -> stVFLCxt .aFTLCxtVbn [1 ] = FTL_CTX_VBLK_IND ;
133
- vfl_meta -> stVFLCxt .aFTLCxtVbn [2 ] = FTL_CTX_VBLK_IND ;
134
-
135
- write_page ((uint8_t * )vfl_meta , (uint8_t * )vfl_ctx_spare , bank , 35 * PAGES_PER_BLOCK );
208
+ // store some copies of the VFL
209
+ for (uint8_t wPageIdx = 0 ; wPageIdx < VFL_NUM_OF_VFL_CXT_COPIES ; wPageIdx ++ ) {
210
+ write_page ((uint8_t * )vfl_meta , (uint8_t * )vfl_ctx_spare , wCSIdx , VFL_CTX_PHYSICAL_PAGE + wPageIdx );
211
+ }
136
212
}
137
213
}
138
214
139
215
void write_ftl_context () {
140
- uint32_t bank , pn ;
216
+ uint16_t cs ;
217
+ uint32_t ppn ;
141
218
142
219
// set the FTL spare type of the first page of the FTL CXT block to indicate that there is a CTX index
143
220
VFLSpare * vfl_ctx_spare = (VFLSpare * )calloc (BYTES_PER_SPARE , sizeof (char ));
144
221
vfl_ctx_spare -> bSpareType = FTL_SPARE_TYPE_CXT_INDEX ;
145
- vfl_ctx_spare -> eccMarker = 0xff ;
146
- get_physical_address ( (FTL_CXT_SECTION_START + FTL_CTX_VBLK_IND ) * PAGES_PER_SUBLOCK , & bank , & pn );
147
- write_page (NULL , (uint8_t * )vfl_ctx_spare , 0 , pn );
222
+ write_page (NULL , (uint8_t * )vfl_ctx_spare , 0 , 0 );
148
223
149
224
// create the FTL Meta page on the last page of the FTL Cxt block and embed the right versions
150
225
FTLMeta * ftl_meta = (FTLMeta * )calloc (sizeof (FTLMeta ), sizeof (char ));
@@ -164,90 +239,86 @@ void write_ftl_context() {
164
239
165
240
// prepare the logical block -> virtual block mapping tables
166
241
for (int i = 0 ; i < MAX_NUM_OF_MAP_TABLES ; i ++ ) {
167
- ftl_meta -> stFTLCxt .adwMapTablePtrs [i ] = i + 1 ; // the mapping will start from the 2nd page in the FTL context block
242
+ ftl_meta -> stFTLCxt .adwMapTablePtrs [i ] = i + 5 ; // the mapping will start from the 2nd page in the FTL context block
168
243
169
244
uint16_t * mapping_page = calloc (BYTES_PER_PAGE / sizeof (uint16_t ), sizeof (uint16_t ));
170
- for (int ind_in_map = 0 ; ind_in_map < 1024 ; ind_in_map ++ ) {
171
- mapping_page [ind_in_map ] = (i * 1024 ) + ind_in_map + 1 ;
245
+ uint32_t items_per_map = BYTES_PER_PAGE / sizeof (uint16_t );
246
+ for (int ind_in_map = 0 ; ind_in_map < items_per_map ; ind_in_map ++ ) {
247
+ mapping_page [ind_in_map ] = (i * items_per_map ) + ind_in_map + 1 ;
172
248
}
173
- get_physical_address ( FTL_CXT_SECTION_START * PAGES_PER_SUBLOCK + i + 1 , & bank , & pn );
174
- write_page ((uint8_t * )mapping_page , NULL , bank , pn );
249
+ _Vpn2Ppn (i + 5 , & cs , & ppn );
250
+ printf ("Writing logical -> virtual block map page %d to physical page %d @ cs %d\n" , i , ppn , cs );
251
+ write_page ((uint8_t * )mapping_page , NULL , cs , ppn );
175
252
}
176
253
177
254
vfl_ctx_spare = (VFLSpare * )calloc (BYTES_PER_SPARE , sizeof (char ));
178
255
vfl_ctx_spare -> bSpareType = FTL_SPARE_TYPE_CXT_INDEX ;
179
- get_physical_address ( (FTL_CXT_SECTION_START + FTL_CTX_VBLK_IND + 1 ) * PAGES_PER_SUBLOCK - 1 , & bank , & pn );
180
- printf ("Writing FTL Meta to virtual page %d\n" , (FTL_CXT_SECTION_START + FTL_CTX_VBLK_IND + 1 ) * PAGES_PER_SUBLOCK - 1 );
181
- write_page ((uint8_t * )ftl_meta , (uint8_t * )vfl_ctx_spare , bank , pn );
256
+
257
+ // we place the FTL Meta on the last page of the first virtual block.
258
+ _Vpn2Ppn (PAGES_PER_SUBLOCK - 1 , & cs , & ppn );
259
+
260
+ printf ("Writing FTL Meta to physical page %d @ cs %d\n" , ppn , cs );
261
+ write_page ((uint8_t * )ftl_meta , (uint8_t * )vfl_ctx_spare , cs , ppn );
182
262
}
183
263
184
264
uint32_t write_hfs_partition (char * filename , uint32_t page_offset ) {
185
265
// write the HFS+ partition to the first page and update the associated spare
186
- uint32_t bank , pn , vpn ;
266
+ uint16_t cs ; uint32_t ppn ;
267
+
187
268
FILE * hfs_file = fopen (filename , "rb" );
188
269
fseek (hfs_file , 0L , SEEK_END );
189
270
int partition_size = ftell (hfs_file );
190
271
fclose (hfs_file );
191
272
192
273
hfs_file = fopen (filename , "rb" );
193
- vpn = (FTL_CXT_SECTION_START + 1 ) * PAGES_PER_SUBLOCK + page_offset ;
194
- for (int i = 0 ; i < partition_size / BYTES_PER_PAGE ; i ++ ) {
274
+ int lpn = page_offset ;
275
+ uint8_t * spare = get_valid_ftl_spare ();
276
+ uint32_t required_pages_for_partition = partition_size / BYTES_PER_PAGE ;
277
+ printf ("Writing HFS partition using %d pages...\n" , required_pages_for_partition );
278
+ for (int i = 0 ; i < required_pages_for_partition ; i ++ ) {
195
279
uint8_t * page = malloc (BYTES_PER_PAGE );
196
280
fread (page , BYTES_PER_PAGE , sizeof (uint8_t ), hfs_file );
197
- VFLSpare * spare = (VFLSpare * )calloc (BYTES_PER_SPARE , sizeof (char ));
198
- spare -> eccMarker = 0xff ;
199
- get_physical_address (vpn , & bank , & pn );
200
- if (i == 0 ) {
201
- printf ("Writing HFS partition to virtual page %d (writing to bank %d, page %d)\n" , vpn , bank , pn );
202
- }
203
- write_page (page , (uint8_t * )spare , bank , pn );
204
- vpn ++ ;
281
+ _Lpn2Ppn (lpn , & cs , & ppn );
282
+ printf ("Writing HFS partition to physical page %d @ cs %d\n" , ppn , cs );
283
+ write_page (page , spare , cs , ppn );
284
+ lpn ++ ;
285
+
286
+ //if(i == 2000) break;
205
287
}
206
288
fclose (hfs_file );
207
289
208
- return partition_size / BYTES_PER_PAGE ;
290
+ return required_pages_for_partition ;
209
291
}
210
292
211
293
void write_mbr (int boot_partition_size ) {
212
- uint32_t bank , pn ;
294
+ uint16_t cs ; uint32_t ppn ;
213
295
214
296
// write the MBR bytes (LBA 0)
215
297
uint8_t * mbr_page = malloc (BYTES_PER_PAGE );
216
- get_physical_address ((FTL_CXT_SECTION_START + 1 ) * PAGES_PER_SUBLOCK , & bank , & pn );
217
298
struct mbr_partition * boot_partition = (struct mbr_partition * )(mbr_page + MBR_ADDRESS );
218
299
boot_partition -> sysid = 0xEE ;
219
300
boot_partition -> startlba = BOOT_PARTITION_FIRST_PAGE ;
220
301
boot_partition -> size = boot_partition_size ;
221
302
222
- // struct mbr_partition *filesystem_partition = (struct mbr_partition *)(mbr_page + MBR_ADDRESS + sizeof(struct mbr_partition));
223
- // filesystem_partition->sysid = 0xEE;
224
- // filesystem_partition->startlba = filesystem_partition_offset;
225
- // filesystem_partition->size = filesystem_partition_size;
226
-
227
303
mbr_page [510 ] = 0x55 ;
228
304
mbr_page [511 ] = 0xAA ;
229
305
230
- VFLSpare * spare = (VFLSpare * )calloc (BYTES_PER_SPARE , sizeof (char ));
231
- spare -> eccMarker = 0xff ;
232
-
233
- printf ("Writing MBR to page %d, bank %d\n" , pn , bank );
234
- write_page (mbr_page , (uint8_t * )spare , bank , pn );
306
+ _Lpn2Ppn (0 , & cs , & ppn );
307
+ printf ("Writing MBR to physical page %d @ cs %d\n" , ppn , cs );
308
+ uint8_t * spare = get_valid_ftl_spare ();
309
+ write_page (mbr_page , spare , cs , ppn );
235
310
}
236
311
237
312
void write_filesystem () {
238
- int pages_for_boot_partition = write_hfs_partition ("filesystem-readonly.img" , BOOT_PARTITION_FIRST_PAGE );
313
+ uint16_t cs ; uint32_t ppn ;
314
+
315
+ int pages_for_boot_partition = write_hfs_partition ("filesystem-it2g-readonly.img" , BOOT_PARTITION_FIRST_PAGE );
239
316
printf ("Required pages for boot partition: %d\n" , pages_for_boot_partition );
240
- //int pages_for_filesystem_partition = write_hfs_partition("filesystem_full.part", BOOT_PARTITION_FIRST_PAGE + pages_for_boot_partition + 1);
241
- //printf("Required pages for filesystem partition: %d\n", pages_for_filesystem_partition);
242
317
243
318
// initialize the EFI header (LBA 1)
244
- uint32_t bank , pn ;
245
319
uint8_t * gpt_header_page = malloc (BYTES_PER_PAGE );
246
320
gpt_hdr * gpt_header = (gpt_hdr * )gpt_header_page ;
247
321
248
- VFLSpare * spare = (VFLSpare * )calloc (BYTES_PER_SPARE , sizeof (char ));
249
- spare -> eccMarker = 0xff ;
250
-
251
322
// create the boot partition entry (LBA 2)
252
323
uint8_t * gpt_entry_boot_partition_page = malloc (BYTES_PER_PAGE );
253
324
gpt_ent * gpt_entry_boot_partition = (gpt_ent * )gpt_entry_boot_partition_page ;
@@ -259,9 +330,10 @@ void write_filesystem() {
259
330
gpt_entry_boot_partition -> ent_lba_end = BOOT_PARTITION_FIRST_PAGE + pages_for_boot_partition ;
260
331
printf ("Boot system partition located on page %lld - %lld\n" , gpt_entry_boot_partition -> ent_lba_start , gpt_entry_boot_partition -> ent_lba_end );
261
332
262
- get_physical_address ((FTL_CXT_SECTION_START + 1 ) * PAGES_PER_SUBLOCK + 2 , & bank , & pn );
263
- printf ("Writing GUID boot partition entry to page %d, bank %d\n" , pn , bank );
264
- write_page (gpt_entry_boot_partition_page , (uint8_t * )spare , bank , pn );
333
+ _Lpn2Ppn (2 , & cs , & ppn );
334
+ printf ("Writing GUID boot partition entry to page %d @ cs %d\n" , ppn , cs );
335
+ uint8_t * spare = get_valid_ftl_spare ();
336
+ write_page (gpt_entry_boot_partition_page , spare , cs , ppn );
265
337
266
338
// finalize the GPT header
267
339
// TODO add the secondary GPT entry!
@@ -274,26 +346,31 @@ void write_filesystem() {
274
346
gpt_header -> hdr_crc_table = crc32 (gpt_entry_boot_partition_page , sizeof (gpt_ent ));
275
347
gpt_header -> hdr_crc_self = crc32 ((uint8_t * )gpt_header , 0x5C );
276
348
277
- get_physical_address (( FTL_CXT_SECTION_START + 1 ) * PAGES_PER_SUBLOCK + 1 , & bank , & pn );
278
- printf ("Writing GUID header to page %d, bank %d\n" , pn , bank );
279
- write_page (gpt_header_page , ( uint8_t * ) spare , bank , pn );
349
+ _Lpn2Ppn ( 1 , & cs , & ppn );
350
+ printf ("Writing GUID header to page %d @ cs %d\n" , ppn , cs );
351
+ write_page (gpt_header_page , spare , cs , ppn );
280
352
281
353
// finally, write the MBR
282
354
write_mbr (pages_for_boot_partition );
283
355
}
284
356
285
357
int main (int argc , char * argv []) {
286
-
287
358
// create the output dir if it does not exist
288
359
struct stat st = {0 };
289
360
290
361
if (stat ("nand" , & st ) == -1 ) {
291
362
mkdir ("nand" , 0700 );
292
363
}
293
364
294
- write_fil_id ();
295
- write_bbts ();
296
365
write_vfl_context ();
297
366
write_ftl_context ();
367
+ write_nand_sig_page ();
368
+ write_bbts ();
298
369
write_filesystem ();
370
+
371
+ // testing
372
+ uint32_t num = 49188 ;
373
+ uint16_t cs ; uint32_t ppn ;
374
+ _Lpn2Ppn (num , & cs , & ppn );
375
+ printf ("LPN %d => %d, cs %d\n" , num , ppn , cs );
299
376
}
0 commit comments