Print this page
patch tsoome-feedback
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/fm/modules/sun4u/cpumem-diagnosis/cmd_oplerr.c
+++ new/usr/src/cmd/fm/modules/sun4u/cpumem-diagnosis/cmd_oplerr.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
↓ open down ↓ |
16 lines elided |
↑ open up ↑ |
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 24 * Use is subject to license terms.
25 25 */
26 26
27 -#pragma ident "%Z%%M% %I% %E% SMI"
28 -
29 27 /*
30 28 * OPL platform specific functions for
31 29 * CPU/Memory error diagnosis engine.
32 30 */
33 31 #include <cmd.h>
34 32 #include <cmd_dimm.h>
35 33 #include <cmd_bank.h>
36 34 #include <cmd_page.h>
37 35 #include <cmd_opl.h>
38 36 #include <string.h>
39 37 #include <errno.h>
40 38 #include <fcntl.h>
41 39 #include <unistd.h>
42 40 #include <dirent.h>
43 41 #include <sys/stat.h>
44 42
45 43 #include <sys/fm/protocol.h>
46 44 #include <sys/fm/io/opl_mc_fm.h>
47 45 #include <sys/async.h>
48 46 #include <sys/opl_olympus_regs.h>
49 47 #include <sys/fm/cpu/SPARC64-VI.h>
50 48 #include <sys/int_const.h>
51 49 #include <sys/mutex.h>
52 50 #include <sys/dditypes.h>
53 51 #include <opl/sys/mc-opl.h>
54 52
55 53 /*
56 54 * The following is the common function for handling
57 55 * memory UE with EID=MEM.
58 56 * The error could be detected by either CPU/IO.
59 57 */
60 58 cmd_evdisp_t
61 59 opl_ue_mem(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl,
62 60 int hdlr_type)
63 61 {
64 62 nvlist_t *rsrc = NULL, *asru = NULL, *fru = NULL;
65 63 uint64_t ubc_ue_log_reg, pa;
66 64 cmd_page_t *page;
67 65
68 66 if (nvlist_lookup_nvlist(nvl,
69 67 FM_EREPORT_PAYLOAD_NAME_RESOURCE, &rsrc) != 0)
70 68 return (CMD_EVD_BAD);
71 69
72 70 switch (hdlr_type) {
73 71 case CMD_OPL_HDLR_CPU:
74 72
75 73 if (nvlist_lookup_uint64(nvl,
76 74 FM_EREPORT_PAYLOAD_NAME_SFAR, &pa) != 0)
77 75 return (CMD_EVD_BAD);
78 76
79 77 fmd_hdl_debug(hdl, "cmd_ue_mem: pa=%llx\n",
80 78 (u_longlong_t)pa);
81 79 break;
82 80
83 81 case CMD_OPL_HDLR_IO:
84 82
85 83 if (nvlist_lookup_uint64(nvl, OBERON_UBC_MUE,
86 84 &ubc_ue_log_reg) != 0)
87 85 return (CMD_EVD_BAD);
88 86
89 87 pa = (ubc_ue_log_reg & UBC_UE_ADR_MASK);
90 88
91 89 fmd_hdl_debug(hdl, "cmd_ue_mem: ue_log_reg=%llx\n",
92 90 (u_longlong_t)ubc_ue_log_reg);
93 91 fmd_hdl_debug(hdl, "cmd_ue_mem: pa=%llx\n",
94 92 (u_longlong_t)pa);
95 93 break;
96 94
97 95 default:
98 96
99 97 return (CMD_EVD_BAD);
100 98 }
101 99
102 100 if ((page = cmd_page_lookup(pa)) != NULL &&
103 101 page->page_case.cc_cp != NULL &&
104 102 fmd_case_solved(hdl, page->page_case.cc_cp))
105 103 return (CMD_EVD_REDUND);
106 104
107 105 if (nvlist_dup(rsrc, &asru, 0) != 0) {
108 106 fmd_hdl_debug(hdl, "opl_ue_mem nvlist dup failed\n");
109 107 return (CMD_EVD_BAD);
110 108 }
111 109
112 110 if (fmd_nvl_fmri_expand(hdl, asru) < 0) {
113 111 nvlist_free(asru);
114 112 CMD_STAT_BUMP(bad_mem_asru);
115 113 return (CMD_EVD_BAD);
116 114 }
117 115
118 116 if ((fru = opl_mem_fru_create(hdl, asru)) == NULL) {
119 117 nvlist_free(asru);
120 118 return (CMD_EVD_BAD);
121 119 }
122 120
123 121 cmd_page_fault(hdl, asru, fru, ep, pa);
124 122 nvlist_free(asru);
125 123 nvlist_free(fru);
126 124 return (CMD_EVD_OK);
127 125 }
128 126
129 127 /*
130 128 * The following is the main function to handle generating
131 129 * the sibling cpu suspect list for the CPU detected UE
132 130 * error cases. This is to handle the
133 131 * multiple strand/core architecture on the OPL platform.
134 132 */
135 133 cmd_evdisp_t
136 134 cmd_opl_ue_cpu(fmd_hdl_t *hdl, fmd_event_t *ep,
137 135 const char *class, const char *fltname,
138 136 cmd_ptrsubtype_t ptr, cmd_cpu_t *cpu,
139 137 cmd_case_t *cc, uint8_t cpumask)
140 138 {
141 139 const char *uuid;
142 140 cmd_cpu_t *main_cpu, *sib_cpu;
143 141 nvlist_t *fmri;
144 142 cmd_list_t *cpu_list;
145 143 opl_cpu_t *opl_cpu;
146 144 uint32_t main_cpuid, nsusp = 1;
147 145 uint8_t cert;
148 146
149 147 fmd_hdl_debug(hdl,
150 148 "Enter OPL_CPUUE_HANDLER for class %x\n", class);
151 149
152 150 main_cpu = cpu;
153 151 main_cpuid = cpu->cpu_cpuid;
154 152
155 153 if (strcmp(fltname, "core") == 0)
156 154 cpu_list = opl_cpulist_insert(hdl, cpu->cpu_cpuid,
157 155 IS_CORE);
158 156 else if (strcmp(fltname, "chip") == 0)
159 157 cpu_list = opl_cpulist_insert(hdl, cpu->cpu_cpuid,
160 158 IS_CHIP);
161 159 else
162 160 cpu_list = opl_cpulist_insert(hdl, cpu->cpu_cpuid,
163 161 IS_STRAND);
164 162
165 163 for (opl_cpu = cmd_list_next(cpu_list); opl_cpu != NULL;
166 164 opl_cpu = cmd_list_next(opl_cpu)) {
167 165 if (opl_cpu->oc_cpuid == main_cpuid) {
168 166 sib_cpu = main_cpu;
169 167 opl_cpu->oc_cmd_cpu = main_cpu;
170 168 } else {
171 169 fmri = cmd_cpu_fmri_create(opl_cpu->oc_cpuid, cpumask);
172 170 if (fmri == NULL) {
↓ open down ↓ |
134 lines elided |
↑ open up ↑ |
173 171 opl_cpu->oc_cmd_cpu = NULL;
174 172 fmd_hdl_debug(hdl,
175 173 "missing asru, cpuid %u excluded\n",
176 174 opl_cpu->oc_cpuid);
177 175 continue;
178 176 }
179 177
180 178 sib_cpu = cmd_cpu_lookup(hdl, fmri, class,
181 179 CMD_CPU_LEVEL_THREAD);
182 180 if (sib_cpu == NULL || sib_cpu->cpu_faulting) {
183 - if (fmri != NULL)
184 - nvlist_free(fmri);
181 + nvlist_free(fmri);
185 182 opl_cpu->oc_cmd_cpu = NULL;
186 183 fmd_hdl_debug(hdl,
187 184 "cpu not present, cpuid %u excluded\n",
188 185 opl_cpu->oc_cpuid);
189 186 continue;
190 187 }
191 188 opl_cpu->oc_cmd_cpu = sib_cpu;
192 - if (fmri != NULL)
193 - nvlist_free(fmri);
189 + nvlist_free(fmri);
194 190 nsusp++;
195 191 }
196 192 if (cpu->cpu_cpuid == main_cpuid) {
197 193 if (cc->cc_cp != NULL &&
198 194 fmd_case_solved(hdl, cc->cc_cp)) {
199 195 if (cpu_list != NULL)
200 196 opl_cpulist_free(hdl, cpu_list);
201 197 return (CMD_EVD_REDUND);
202 198 }
203 199
204 200 if (cc->cc_cp == NULL)
205 201 cc->cc_cp = cmd_case_create(hdl,
206 202 &cpu->cpu_header, ptr, &uuid);
207 203
208 204 if (cc->cc_serdnm != NULL) {
209 205 fmd_hdl_debug(hdl,
210 206 "destroying existing %s state for class %x\n",
211 207 cc->cc_serdnm, class);
212 208 fmd_serd_destroy(hdl, cc->cc_serdnm);
213 209 fmd_hdl_strfree(hdl, cc->cc_serdnm);
214 210 cc->cc_serdnm = NULL;
215 211 fmd_case_reset(hdl, cc->cc_cp);
216 212 }
217 213 fmd_case_add_ereport(hdl, cc->cc_cp, ep);
218 214 }
219 215 }
220 216 cert = opl_avg(100, nsusp);
221 217 for (opl_cpu = cmd_list_next(cpu_list); opl_cpu != NULL;
222 218 opl_cpu = cmd_list_next(opl_cpu)) {
223 219 if (opl_cpu->oc_cmd_cpu != NULL) {
224 220 nvlist_t *cpu_rsrc;
225 221
226 222 cpu_rsrc = opl_cpursrc_create(hdl, opl_cpu->oc_cpuid);
227 223 if (cpu_rsrc == NULL) {
228 224 fmd_hdl_debug(hdl,
229 225 "missing rsrc, cpuid %u excluded\n",
230 226 opl_cpu->oc_cpuid);
231 227 continue;
232 228 }
233 229 cmd_cpu_create_faultlist(hdl, cc->cc_cp,
234 230 opl_cpu->oc_cmd_cpu, fltname, cpu_rsrc, cert);
235 231 nvlist_free(cpu_rsrc);
236 232 }
237 233 }
238 234 fmd_case_solve(hdl, cc->cc_cp);
239 235 if (cpu_list != NULL)
240 236 opl_cpulist_free(hdl, cpu_list);
241 237 return (CMD_EVD_OK);
242 238 }
243 239
244 240 /*
245 241 * Generates DIMM fault if the number of Permanent CE
246 242 * threshold is exceeded.
247 243 */
248 244 static void
249 245 opl_ce_thresh_check(fmd_hdl_t *hdl, cmd_dimm_t *dimm)
250 246 {
251 247 nvlist_t *dflt;
252 248 fmd_case_t *cp;
253 249
254 250 fmd_hdl_debug(hdl,
255 251 "Permanent CE event threshold checking.\n");
256 252
257 253 if (dimm->dimm_flags & CMD_MEM_F_FAULTING) {
258 254 /* We've already complained about this DIMM */
259 255 return;
260 256 }
261 257
262 258 if (dimm->dimm_nretired >= fmd_prop_get_int32(hdl,
263 259 "max_perm_ce_dimm")) {
264 260 dimm->dimm_flags |= CMD_MEM_F_FAULTING;
265 261 cp = fmd_case_open(hdl, NULL);
266 262 dflt = cmd_dimm_create_fault(hdl, dimm, "fault.memory.dimm",
267 263 CMD_FLTMAXCONF);
268 264 fmd_case_add_suspect(hdl, cp, dflt);
269 265 fmd_case_solve(hdl, cp);
270 266 }
271 267 }
272 268
273 269 /*
274 270 * Notify fault page information (pa and errlog) to XSCF via mc-opl
275 271 */
276 272 #define MC_PHYDEV_DIR "/devices"
277 273 #define MC_PHYPREFIX "pseudo-mc@"
278 274 static int
279 275 opl_scf_log(fmd_hdl_t *hdl, nvlist_t *nvl)
280 276 {
281 277 uint32_t *eadd, *elog;
282 278 uint_t n;
283 279 uint64_t pa;
284 280 char path[MAXPATHLEN];
285 281 char *unum;
286 282 nvlist_t *rsrc;
287 283 DIR *mcdir;
288 284 struct dirent *dp;
289 285 mc_flt_page_t flt_page;
290 286 cmd_page_t *page;
291 287 struct stat statbuf;
292 288
293 289 /*
294 290 * Extract ereport.
295 291 * Sanity check of pa is already done at cmd_opl_mac_common().
296 292 * mc-opl sets only one entry for MC_OPL_ERR_ADD, MC_OPL_ERR_LOG,
297 293 * and MC_OPL_BANK.
298 294 */
299 295 if ((nvlist_lookup_uint64(nvl, MC_OPL_PA, &pa) != 0) ||
300 296 (nvlist_lookup_uint32_array(nvl, MC_OPL_ERR_ADD, &eadd, &n) != 0) ||
301 297 (nvlist_lookup_uint32_array(nvl, MC_OPL_ERR_LOG, &elog, &n) != 0)) {
302 298 fmd_hdl_debug(hdl, "opl_scf_log failed to extract ereport.\n");
303 299 return (-1);
304 300 }
305 301 if (nvlist_lookup_nvlist(nvl, FM_EREPORT_PAYLOAD_NAME_RESOURCE,
306 302 &rsrc) != 0) {
307 303 fmd_hdl_debug(hdl, "opl_scf_log failed to get resource.\n");
308 304 return (-1);
309 305 }
310 306 if (nvlist_lookup_string(rsrc, FM_FMRI_MEM_UNUM, &unum) != 0) {
311 307 fmd_hdl_debug(hdl, "opl_scf_log failed to get unum.\n");
312 308 return (-1);
313 309 }
314 310
315 311 page = cmd_page_lookup(pa);
316 312 if (page != NULL && page->page_flags & CMD_MEM_F_FAULTING) {
317 313 /*
318 314 * fault.memory.page will not be created.
319 315 */
320 316 return (0);
321 317 }
322 318
323 319 flt_page.err_add = eadd[0];
324 320 flt_page.err_log = elog[0];
325 321 flt_page.fmri_addr = (uint64_t)(uint32_t)unum;
326 322 flt_page.fmri_sz = strlen(unum) + 1;
327 323
328 324 fmd_hdl_debug(hdl, "opl_scf_log DIMM: %s (%d)\n",
329 325 unum, strlen(unum) + 1);
330 326 fmd_hdl_debug(hdl, "opl_scf_log pa:%llx add:%x log:%x\n",
331 327 pa, eadd[0], elog[0]);
332 328
333 329 if ((mcdir = opendir(MC_PHYDEV_DIR)) != NULL) {
334 330 while ((dp = readdir(mcdir)) != NULL) {
335 331 int fd;
336 332
337 333 if (strncmp(dp->d_name, MC_PHYPREFIX,
338 334 strlen(MC_PHYPREFIX)) != 0)
339 335 continue;
340 336
341 337 (void) snprintf(path, sizeof (path),
342 338 "%s/%s", MC_PHYDEV_DIR, dp->d_name);
343 339
344 340 if (stat(path, &statbuf) != 0 ||
345 341 (statbuf.st_mode & S_IFCHR) == 0) {
346 342 /* skip if not a character device */
347 343 continue;
348 344 }
349 345
350 346 if ((fd = open(path, O_RDONLY)) < 0)
351 347 continue;
352 348
353 349 if (ioctl(fd, MCIOC_FAULT_PAGE, &flt_page) == 0) {
354 350 fmd_hdl_debug(hdl, "opl_scf_log ioctl(%s)\n",
355 351 path);
356 352 (void) close(fd);
357 353 (void) closedir(mcdir);
358 354 return (0);
359 355 }
360 356 (void) close(fd);
361 357 }
362 358 (void) closedir(mcdir);
363 359 }
364 360
365 361 fmd_hdl_debug(hdl, "opl_scf_log failed ioctl().\n");
366 362
367 363 return (-1);
368 364 }
369 365
370 366 /*
371 367 * This is the common function for processing MAC detected
372 368 * Intermittent and Permanent CEs.
373 369 */
374 370
375 371 cmd_evdisp_t
376 372 cmd_opl_mac_ce(fmd_hdl_t *hdl, fmd_event_t *ep, const char *class,
377 373 nvlist_t *asru, nvlist_t *fru, uint64_t pa, nvlist_t *nvl)
378 374 {
379 375 cmd_dimm_t *dimm;
380 376 const char *uuid;
381 377
382 378 fmd_hdl_debug(hdl,
383 379 "Processing CE ereport\n");
384 380
385 381 if ((dimm = cmd_dimm_lookup(hdl, asru)) == NULL &&
386 382 (dimm = cmd_dimm_create(hdl, asru)) == NULL)
387 383 return (CMD_EVD_UNUSED);
388 384
389 385 if (dimm->dimm_case.cc_cp == NULL) {
390 386 dimm->dimm_case.cc_cp = cmd_case_create(hdl,
391 387 &dimm->dimm_header, CMD_PTR_DIMM_CASE, &uuid);
392 388 }
393 389
394 390 if (strcmp(class, "ereport.asic.mac.ptrl-ice") == 0) {
395 391 CMD_STAT_BUMP(ce_interm);
396 392 fmd_hdl_debug(hdl, "adding FJ-Intermittent event "
397 393 "to CE serd engine\n");
398 394
399 395 if (dimm->dimm_case.cc_serdnm == NULL) {
400 396 dimm->dimm_case.cc_serdnm =
401 397 cmd_mem_serdnm_create(hdl,
402 398 "dimm", dimm->dimm_unum);
403 399 fmd_serd_create(hdl, dimm->dimm_case.cc_serdnm,
404 400 fmd_prop_get_int32(hdl, "ce_n"),
405 401 fmd_prop_get_int64(hdl, "ce_t"));
406 402 }
407 403
408 404 if (fmd_serd_record(hdl, dimm->dimm_case.cc_serdnm, ep) ==
409 405 FMD_B_FALSE) {
410 406 return (CMD_EVD_OK); /* engine hasn't fired */
411 407 }
412 408 fmd_hdl_debug(hdl, "ce serd fired\n");
413 409 fmd_case_add_serd(hdl, dimm->dimm_case.cc_cp,
414 410 dimm->dimm_case.cc_serdnm);
415 411 fmd_serd_reset(hdl, dimm->dimm_case.cc_serdnm);
416 412
417 413 (void) opl_scf_log(hdl, nvl);
418 414 } else {
419 415 CMD_STAT_BUMP(ce_sticky);
420 416 }
421 417
422 418 dimm->dimm_nretired++;
423 419 dimm->dimm_retstat.fmds_value.ui64++;
424 420 cmd_dimm_dirty(hdl, dimm);
425 421
426 422 cmd_page_fault(hdl, asru, fru, ep, pa);
427 423 opl_ce_thresh_check(hdl, dimm);
428 424
429 425 return (CMD_EVD_OK);
430 426 }
431 427
432 428 /*
433 429 * This is the common entry for processing MAC detected errors.
434 430 * It is responsible for generating the memory page fault event.
435 431 * The permanent CE (sticky) in normal mode is handled here also
436 432 * in the same way as in the UE case.
437 433 */
438 434 /*ARGSUSED*/
439 435 cmd_evdisp_t
440 436 cmd_opl_mac_common(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl,
441 437 const char *class, cmd_errcl_t clcode)
442 438 {
443 439 uint64_t pa;
444 440 nvlist_t *rsrc = NULL, *asru = NULL, *fru = NULL;
445 441 cmd_page_t *page;
446 442
447 443 fmd_hdl_debug(hdl, "cmd_mac_common: clcode=%ll\n", clcode);
448 444
449 445 if (nvlist_lookup_nvlist(nvl, MC_OPL_RESOURCE, &rsrc) != 0)
450 446 return (CMD_EVD_BAD);
451 447
452 448 if (nvlist_lookup_uint64(nvl, MC_OPL_PA, &pa)
453 449 != 0)
454 450 return (CMD_EVD_BAD);
455 451
456 452 /*
457 453 * Check for invalid pa.
458 454 * The most sig. bit should not be on.
459 455 * It would be out of the range of possible pa
460 456 * in MAC's view.
461 457 */
462 458 if (((uint64_t)1 << 63) & pa)
463 459 return (CMD_EVD_BAD);
464 460
465 461 if ((page = cmd_page_lookup(pa)) != NULL &&
466 462 page->page_case.cc_cp != NULL &&
467 463 fmd_case_solved(hdl, page->page_case.cc_cp))
468 464 return (CMD_EVD_REDUND);
469 465
470 466 if (nvlist_dup(rsrc, &asru, 0) != 0) {
471 467 fmd_hdl_debug(hdl, "cmd_opl_mac_common nvlist dup failed\n");
472 468 return (CMD_EVD_BAD);
473 469 }
474 470
475 471 if (fmd_nvl_fmri_expand(hdl, asru) < 0) {
476 472 fmd_hdl_debug(hdl, "cmd_opl_mac_common expand failed\n");
477 473 nvlist_free(asru);
478 474 CMD_STAT_BUMP(bad_mem_asru);
479 475 return (CMD_EVD_BAD);
480 476 }
481 477
482 478 if ((fru = opl_mem_fru_create(hdl, asru)) == NULL) {
483 479 fmd_hdl_debug(hdl, "cmd_opl_mac_common fru_create failed\n");
484 480 nvlist_free(asru);
485 481 return (CMD_EVD_BAD);
486 482 }
487 483
488 484 /*
489 485 * process PCE and ICE to create DIMM fault
490 486 */
491 487 if (strcmp(class, "ereport.asic.mac.mi-ce") == 0 ||
492 488 strcmp(class, "ereport.asic.mac.ptrl-ce") == 0 ||
493 489 strcmp(class, "ereport.asic.mac.ptrl-ice") == 0) {
494 490 cmd_evdisp_t ret;
495 491
496 492 ret = cmd_opl_mac_ce(hdl, ep, class, asru, fru, pa, nvl);
497 493 nvlist_free(asru);
498 494 nvlist_free(fru);
499 495 if (ret != CMD_EVD_OK) {
500 496 fmd_hdl_debug(hdl,
501 497 "cmd_opl_mac_common: mac_ce failed\n");
502 498 return (CMD_EVD_BAD);
503 499 } else
504 500 return (CMD_EVD_OK);
505 501 }
506 502
507 503 /* The following code handles page retires for UEs and CMPEs. */
508 504
509 505 cmd_page_fault(hdl, asru, fru, ep, pa);
510 506 nvlist_free(asru);
511 507 nvlist_free(fru);
512 508 return (CMD_EVD_OK);
513 509 }
514 510
515 511 /*
516 512 * Common entry points for handling CPU/IO detected UE with
517 513 * respect to EID=MEM.
518 514 */
519 515 /*ARGSUSED*/
520 516 cmd_evdisp_t
521 517 cmd_opl_cpu_mem(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl,
522 518 const char *class, cmd_errcl_t clcode)
523 519 {
524 520 return (opl_ue_mem(hdl, ep, nvl, CMD_OPL_HDLR_CPU));
525 521 }
526 522
527 523 /*ARGSUSED*/
528 524 cmd_evdisp_t
529 525 cmd_opl_io_mem(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl,
530 526 const char *class, cmd_errcl_t clcode)
531 527 {
532 528 return (opl_ue_mem(hdl, ep, nvl, CMD_OPL_HDLR_IO));
533 529 }
↓ open down ↓ |
330 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX