Print this page
fixup .text where possible
7127 remove -Wno-missing-braces from Makefile.uts
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/uts/common/io/cpqary3/cpqary3.c
+++ new/usr/src/uts/common/io/cpqary3/cpqary3.c
1 1 /*
2 2 * This file and its contents are supplied under the terms of the
3 3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 4 * You may only use this file in accordance with the terms of version
5 5 * 1.0 of the CDDL.
6 6 *
7 7 * A full copy of the text of the CDDL should have accompanied this
8 8 * source. A copy of the CDDL is also available via the Internet at
9 9 * http://www.illumos.org/license/CDDL.
10 10 */
11 11
12 12 /*
13 13 * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
14 14 */
15 15
16 16 #include "cpqary3.h"
17 17
18 18 /*
19 19 * Local Autoconfiguration Function Prototype Declations
20 20 */
21 21
22 22 int cpqary3_attach(dev_info_t *, ddi_attach_cmd_t);
23 23 int cpqary3_detach(dev_info_t *, ddi_detach_cmd_t);
24 24 int cpqary3_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
25 25
26 26 /*
27 27 * Local Functions Definitions
28 28 */
29 29
30 30 static void cpqary3_cleanup(cpqary3_t *, uint32_t);
31 31 static uint8_t cpqary3_update_ctlrdetails(cpqary3_t *, uint32_t *);
32 32 int8_t cpqary3_detect_target_geometry(cpqary3_t *);
33 33
34 34 /*
35 35 * External Variable Definitions
36 36 */
37 37
38 38 extern cpqary3_driver_info_t gdriver_info;
39 39
40 40 /*
41 41 * Global Variables Definitions
42 42 */
43 43
44 44 static char cpqary3_brief[] = "HP Smart Array Driver";
45 45 void *cpqary3_state;
46 46
47 47 /* HPQaculi Changes */
48 48
49 49 /*
50 50 * HBA minor number schema
51 51 *
52 52 * The minor numbers for any minor device nodes that we create are
53 53 * governed by the SCSA framework. We use the macros below to
54 54 * fabricate minor numbers for nodes that we own.
55 55 *
56 56 * See sys/impl/transport.h for more info.
57 57 */
58 58
59 59 /* Macro to extract interface from minor number */
60 60 #define CPQARY3_MINOR2INTERFACE(_x) ((_x) & (TRAN_MINOR_MASK))
61 61
62 62 /* Base of range assigned to HBAs: */
63 63 #define SCSA_MINOR_HBABASE (32)
64 64
65 65 /* Our minor nodes: */
66 66 #define CPQARY3_MINOR (0 + SCSA_MINOR_HBABASE)
67 67
68 68 /* Convenience macros to convert device instances to minor numbers */
69 69 #define CPQARY3_INST2x(_i, _x) (((_i) << INST_MINOR_SHIFT) | (_x))
70 70 #define CPQARY3_INST2CPQARY3(_i) CPQARY3_INST2x(_i, CPQARY3_MINOR)
71 71
72 72 /* HPQacucli Changes */
73 73
74 74 /*
75 75 * The Driver DMA Limit structure.
76 76 * Data used for SMART Integrated Array Controller shall be used.
77 77 */
78 78
79 79 ddi_dma_attr_t cpqary3_dma_attr = {
80 80 DMA_ATTR_V0, /* ddi_dma_attr version */
81 81 0, /* Low Address */
82 82 0xFFFFFFFFFFFFFFFF, /* High Address */
83 83 0x00FFFFFF, /* Max DMA Counter register */
84 84 0x20, /* Byte Alignment */
85 85 0x20, /* Burst Sizes : 32 Byte */
86 86 DMA_UNIT_8, /* Minimum DMA xfer Size */
87 87 0xFFFFFFFF, /* Maximum DMA xfer Size */
88 88 /*
89 89 * Segment boundary restrictions
90 90 * The addr should not cross 4GB boundry.
91 91 * This is required to address an issue
92 92 * in the Surge ASIC, with earlier FW versions.
93 93 */
94 94 0xFFFFFFFF,
95 95 CPQARY3_SG_CNT, /* Scatter/Gather List Length */
96 96 512, /* Device Granularity */
97 97 0 /* DMA flags */
98 98 };
99 99
100 100 /*
101 101 * The Device Access Attribute Structure.
102 102 */
103 103
104 104 ddi_device_acc_attr_t cpqary3_dev_attributes = {
105 105 DDI_DEVICE_ATTR_V0,
106 106 DDI_STRUCTURE_LE_ACC,
107 107 DDI_STRICTORDER_ACC
108 108 };
109 109
110 110 /*
111 111 * Character-Block Operations Structure
112 112 */
113 113
114 114 static struct cb_ops cpqary3_cb_ops = {
115 115 /* HPQacucli Changes */
116 116 scsi_hba_open,
117 117 scsi_hba_close,
118 118 /* HPQacucli Changes */
119 119 nodev, /* cb_strategy */
120 120 nodev, /* cb_print */
121 121 nodev, /* cb_dump */
122 122 nodev, /* cb_read */
123 123 nodev, /* cb_write */
124 124 cpqary3_ioctl, /* cb_ioctl */
125 125 nodev, /* cb_devmap */
126 126 nodev, /* cb_mmap */
127 127 nodev, /* cb_segmap */
128 128 nochpoll, /* cb_chpoll */
129 129 ddi_prop_op, /* cb_prop_op */
130 130 NULL, /* cb_stream */
131 131 (int)(D_NEW|D_MP), /* cb_flag */
132 132 CB_REV,
133 133 nodev,
134 134 nodev
135 135 };
136 136
137 137 /*
138 138 * Device Operations Structure
139 139 */
140 140
141 141 static struct dev_ops cpqary3_dev_ops = {
142 142 DEVO_REV, /* Driver Build Version */
143 143 0, /* Driver reference count */
144 144 nodev, /* Get Info */
145 145 nulldev, /* Identify not required */
146 146 nulldev, /* Probe, obselete for s2.6 and up */
147 147 cpqary3_attach, /* Attach routine */
148 148 cpqary3_detach, /* Detach routine */
149 149 nodev, /* Reset */
150 150 &cpqary3_cb_ops, /* Entry Points for C&B drivers */
151 151 NULL, /* Bus ops */
152 152 nodev /* cpqary3_power */
153 153 };
154 154
155 155 /*
↓ open down ↓ |
155 lines elided |
↑ open up ↑ |
156 156 * Linkage structures
157 157 */
158 158
159 159 static struct modldrv cpqary3_modldrv = {
160 160 &mod_driverops, /* Module Type - driver */
161 161 cpqary3_brief, /* Driver Desc */
162 162 &cpqary3_dev_ops /* Driver Ops */
163 163 };
164 164
165 165 static struct modlinkage cpqary3_modlinkage = {
166 - MODREV_1, /* Loadable module rev. no. */
167 - &cpqary3_modldrv, /* Loadable module */
168 - NULL /* end */
166 + MODREV_1, /* Loadable module rev. no. */
167 + { &cpqary3_modldrv, /* Loadable module */
168 + NULL }
169 169 };
170 170
171 171
172 172 /*
173 173 * Function : _init
174 174 * Description : This routine allocates soft state resources for the
175 175 * driver, registers the HBA with the system and
176 176 * adds the driver(loadable module).
177 177 * Called By : Kernel
178 178 * Parameters : None
179 179 * Return Values: 0 / Non-Zero
180 180 * [as returned by the mod_install OS function]
181 181 */
182 182 int
183 183 _init()
184 184 {
185 185 int retvalue;
186 186
187 187 /*
188 188 * Allocate Soft State Resources; if failure, return.
189 189 */
190 190 retvalue = ddi_soft_state_init(&cpqary3_state,
191 191 sizeof (cpqary3_t), MAX_CTLRS);
192 192 VERIFY(retvalue == 0);
193 193
194 194 /*
195 195 * Initialise the HBA Interface.
196 196 */
197 197 if (!(retvalue = scsi_hba_init(&cpqary3_modlinkage))) {
198 198 /* Load the driver */
199 199 if ((retvalue = mod_install(&cpqary3_modlinkage))) {
200 200 /*
201 201 * Failed to load the driver, undo HBA interface
202 202 * and soft state allocation.
203 203 */
204 204 scsi_hba_fini(&cpqary3_modlinkage);
205 205 ddi_soft_state_fini(&cpqary3_state);
206 206 }
207 207 } else {
208 208 /*
209 209 * Failed to register HBA interface, undo all soft state
210 210 * allocation
211 211 */
212 212 ddi_soft_state_fini(&cpqary3_state);
213 213 }
214 214
215 215 return (retvalue);
216 216 }
217 217
218 218 /*
219 219 * Function : _fini
220 220 * Description : This routine removes the loadable module, cancels the
221 221 * HBA registration and deallocates soft state resources.
222 222 * Called By : Kernel
223 223 * Parameters : None
224 224 * Return Values: 0 - Success / Non-Zero - Failure
225 225 * [as returned by the mod_remove(OS provided) function]
226 226 */
227 227 int
228 228 _fini()
229 229 {
230 230 int retvalue;
231 231
232 232 /* Unload the Driver(loadable module) */
233 233
234 234 if ((retvalue = mod_remove(&cpqary3_modlinkage)) == 0) {
235 235
236 236 /* Cancel the registeration for the HBA Interface */
237 237 scsi_hba_fini(&cpqary3_modlinkage);
238 238
239 239 /* dealloacte soft state resources of the driver */
240 240 ddi_soft_state_fini(&cpqary3_state);
241 241 }
242 242
243 243 return (retvalue);
244 244 }
245 245
246 246 /*
247 247 * Function : _info
248 248 * Description : This routine returns information about the driver.
249 249 * Called By : Kernel
250 250 * Parameters : None
251 251 * Return Values: 0 / Non-Zero
252 252 * [as returned by mod_info(OS provided) function]
253 253 */
254 254 int
255 255 _info(struct modinfo *modinfop)
256 256 {
257 257 /*
258 258 * Get the module information.
259 259 */
260 260 return (mod_info(&cpqary3_modlinkage, modinfop));
261 261 }
262 262
263 263
264 264 /*
265 265 * Function : cpqary3_attach
266 266 * Description : This routine initializes the driver specific soft state
267 267 * structure, initializes the HBA, interrupt handlers,
268 268 * memory pool, timeout handler, various mutex, creates the
269 269 * minor node.
270 270 * Called By : kernel
271 271 * Parameters : dip, command for attach
272 272 * Return Values: DDI_SUCCESS / DDI_FAILURE
273 273 * [Success on overall initialization & configuration
274 274 * being successful. Failure if any of the initialization
275 275 * or any driver-specific mandatory configuration fails]
276 276 */
277 277 int
278 278 cpqary3_attach(dev_info_t *dip, ddi_attach_cmd_t attach_cmd)
279 279 {
280 280 int8_t minor_node_name[14];
281 281 uint32_t instance;
282 282 uint32_t retvalue;
283 283 uint32_t cleanstatus = 0;
284 284 cpqary3_t *cpqary3p; /* per-controller */
285 285 ddi_dma_attr_t tmp_dma_attr;
286 286
287 287 /* Return Failure, If the Command is other than - DDI_ATTACH. */
288 288
289 289 if (attach_cmd != DDI_ATTACH)
290 290 return (DDI_FAILURE);
291 291
292 292 /* Get the Instance of the Device */
293 293
294 294 instance = ddi_get_instance(dip);
295 295
296 296 /* Allocate the per-device-instance soft state structure */
297 297
298 298 retvalue = ddi_soft_state_zalloc(cpqary3_state, instance);
299 299 VERIFY(retvalue == 0);
300 300
301 301 cleanstatus |= CPQARY3_SOFTSTATE_ALLOC_DONE;
302 302
303 303 /* Per Controller Pointer */
304 304 cpqary3p = ddi_get_soft_state(cpqary3_state, instance);
305 305 if (!cpqary3p) {
306 306 cmn_err(CE_WARN, "CPQary3: Soft State Retrieval Failed");
307 307 cpqary3_cleanup(cpqary3p, cleanstatus);
308 308 return (DDI_FAILURE);
309 309 }
310 310
311 311 /* Maintain a record in per-ctlr structure */
312 312 cpqary3p->dip = dip;
313 313 cpqary3p->instance = instance;
314 314
315 315 /* Get the User Configuration information from Driver's conf File */
316 316 cpqary3_read_conf_file(dip, cpqary3p);
317 317
318 318 /* Get and Map the HW Configuration */
319 319 retvalue = cpqary3_update_ctlrdetails(cpqary3p, &cleanstatus);
320 320 if (retvalue == CPQARY3_FAILURE) {
321 321 cpqary3_cleanup(cpqary3p, cleanstatus);
322 322 return (DDI_FAILURE);
323 323 }
324 324
325 325 /* Get the Cookie for hardware Interrupt Handler */
326 326 if (ddi_get_iblock_cookie(dip, 0, &cpqary3p->hw_iblock_cookie) !=
327 327 DDI_SUCCESS) {
328 328 cpqary3_cleanup(cpqary3p, cleanstatus);
329 329 return (DDI_FAILURE);
330 330 }
331 331
332 332 /* Initialize Per Controller Mutex */
333 333 mutex_init(&cpqary3p->hw_mutex, NULL, MUTEX_DRIVER,
334 334 (void *)cpqary3p->hw_iblock_cookie);
335 335
336 336 cleanstatus |= CPQARY3_MUTEX_INIT_DONE;
337 337
338 338 /* Get the Cookie for Soft(low level) Interrupt Handler */
339 339 if (ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_HIGH,
340 340 &cpqary3p->sw_iblock_cookie) != DDI_SUCCESS) {
341 341 cpqary3_cleanup(cpqary3p, cleanstatus);
342 342 return (DDI_FAILURE);
343 343 }
344 344
345 345 /* Initialize the s/w Mutex */
346 346 mutex_init(&cpqary3p->sw_mutex, NULL, MUTEX_DRIVER,
347 347 (void *)cpqary3p->sw_iblock_cookie);
348 348 cleanstatus |= CPQARY3_SW_MUTEX_INIT_DONE;
349 349
350 350 /* Initialize per Controller private details */
351 351 retvalue = cpqary3_init_ctlr_resource(cpqary3p);
352 352 if (retvalue != CPQARY3_SUCCESS) {
353 353 cpqary3_cleanup(cpqary3p, cleanstatus);
354 354 return (DDI_FAILURE);
355 355 }
356 356 cleanstatus |= CPQARY3_CTLR_CONFIG_DONE;
357 357
358 358 /*
359 359 * Allocate HBA transport structure
360 360 */
361 361 cpqary3p->hba_tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);
362 362 if (!cpqary3p->hba_tran) {
363 363 cpqary3_cleanup(cpqary3p, cleanstatus);
364 364 return (DDI_FAILURE);
365 365 }
366 366 cleanstatus |= CPQARY3_HBA_TRAN_ALLOC_DONE;
367 367
368 368 /*
369 369 * Set private field for the HBA tran structure.
370 370 * Initialise the HBA tran entry points.
371 371 * Attach the controller to HBA.
372 372 */
373 373 cpqary3_init_hbatran(cpqary3p);
374 374
375 375 /* PERF */
376 376 /* SG */
377 377 tmp_dma_attr = cpqary3_dma_attr;
378 378 tmp_dma_attr.dma_attr_sgllen = cpqary3p->sg_cnt;
379 379 /* SG */
380 380 /* PERF */
381 381 /*
382 382 * Register the DMA attributes and the transport vectors
383 383 * of each instance of the HBA device.
384 384 */
385 385 if (scsi_hba_attach_setup(dip, &tmp_dma_attr, cpqary3p->hba_tran,
386 386 SCSI_HBA_TRAN_CLONE) == DDI_FAILURE) {
387 387 cpqary3_cleanup(cpqary3p, cleanstatus);
388 388 return (DDI_FAILURE);
389 389 }
390 390 cleanstatus |= CPQARY3_HBA_TRAN_ATTACH_DONE;
391 391
392 392 /*
393 393 * Create a minor node for Ioctl interface.
394 394 * The nomenclature used will be "cpqary3" immediately followed by
395 395 * the current driver instance in the system.
396 396 * for e.g.: for 0th instance : cpqary3,0
397 397 * for 1st instance : cpqary3,1
398 398 */
399 399
400 400 (void) sprintf(minor_node_name, "cpqary3,%d", instance);
401 401
402 402 /* HPQacucli Changes */
403 403 if (ddi_create_minor_node(dip, minor_node_name, S_IFCHR,
404 404 CPQARY3_INST2CPQARY3(instance), DDI_NT_SCSI_NEXUS, 0) ==
405 405 DDI_SUCCESS) {
406 406 /* HPQacucli Changes */
407 407 cleanstatus |= CPQARY3_CREATE_MINOR_NODE;
408 408 } else {
409 409 cmn_err(CE_NOTE, "CPQary3 : Failed to create minor node");
410 410 cpqary3_cleanup(cpqary3p, cleanstatus);
411 411 return (DDI_FAILURE);
412 412 }
413 413
414 414
415 415 /* Register a timeout driver-routine to be called every 2 secs */
416 416 cpqary3p->tick_tmout_id = timeout(cpqary3_tick_hdlr,
417 417 (caddr_t)cpqary3p, drv_usectohz(CPQARY3_TICKTMOUT_VALUE));
418 418 cleanstatus |= CPQARY3_TICK_TMOUT_REGD;
419 419
420 420 /* Register Software Interrupt Handler */
421 421 if (ddi_add_softintr(dip, DDI_SOFTINT_HIGH,
422 422 &cpqary3p->cpqary3_softintr_id, &cpqary3p->sw_iblock_cookie, NULL,
423 423 cpqary3_sw_isr, (caddr_t)cpqary3p) != DDI_SUCCESS) {
424 424 cpqary3_cleanup(cpqary3p, cleanstatus);
425 425 return (DDI_FAILURE);
426 426 }
427 427 cleanstatus |= CPQARY3_SW_INTR_HDLR_SET;
428 428
429 429 /* Register Interrupt Handler */
430 430 if (ddi_add_intr(dip, 0, &cpqary3p->hw_iblock_cookie, NULL,
431 431 cpqary3_hw_isr, (caddr_t)cpqary3p) != DDI_SUCCESS) {
432 432 cpqary3_cleanup(cpqary3p, cleanstatus);
433 433 return (DDI_FAILURE);
434 434 }
435 435 cleanstatus |= CPQARY3_INTR_HDLR_SET;
436 436
437 437 /* Enable the Controller Interrupt */
438 438 cpqary3_intr_onoff(cpqary3p, CPQARY3_INTR_ENABLE);
439 439 if (cpqary3p->host_support & 0x4)
440 440 cpqary3_lockup_intr_onoff(cpqary3p, CPQARY3_LOCKUP_INTR_ENABLE);
441 441
442 442 /*
443 443 * We have come with hmaeventd - which logs the storage events on
444 444 * console as well as in IML. So we are commenting the NOE support in
445 445 * the driver
446 446 */
447 447
448 448 /* NOE */
449 449 if (cpqary3p->noe_support == 1) {
450 450 /* Enable the Notification on Event in this controller */
451 451 if (CPQARY3_SUCCESS ==
452 452 cpqary3_send_NOE_command(cpqary3p,
453 453 NULL, CPQARY3_NOE_INIT)) {
454 454 cleanstatus |= CPQARY3_NOE_INIT_DONE;
455 455 } else {
456 456 cmn_err(CE_CONT, "CPQary3 : Failed to initialize "
457 457 "NOTIFICATION ON EVENT \n");
458 458 }
459 459 }
460 460 /* NOE */
461 461
462 462 /* Report that an Instance of the Driver is Attached Successfully */
463 463 ddi_report_dev(dip);
464 464
465 465 /*
466 466 * Now update the num_ctlr
467 467 * This is required for the agents
468 468 */
469 469
470 470 gdriver_info.num_ctlr++;
471 471
472 472 return (DDI_SUCCESS);
473 473
474 474 }
475 475
476 476 /*
477 477 * Function : cpqary3_detach
478 478 * Description : This routine removes the state associated with a
479 479 * given instance of a device node prior to the
480 480 * removal of that instance from the system
481 481 * Called By : kernel
482 482 * Parameters : dip, command for detach
483 483 * Return Values: DDI_SUCCESS / DDI_FAILURE
484 484 * [failure ONLY if the command sent with this function
485 485 * as a paramter is not DETACH]
486 486 */
487 487 int
488 488 cpqary3_detach(dev_info_t *dip, ddi_detach_cmd_t detach_cmd)
489 489 {
490 490 cpqary3_t *cpqary3p;
491 491 scsi_hba_tran_t *hba_tran;
492 492
493 493 /* Return failure, If Command is not DDI_DETACH */
494 494
495 495 if (DDI_DETACH != detach_cmd)
496 496 return (DDI_FAILURE);
497 497
498 498 /*
499 499 * Get scsi_hba_tran structure.
500 500 * Get per controller structure.
501 501 */
502 502
503 503 hba_tran = (scsi_hba_tran_t *)ddi_get_driver_private(dip);
504 504 cpqary3p = (cpqary3_t *)hba_tran->tran_hba_private;
505 505
506 506 /* Flush the cache */
507 507
508 508 cpqary3_flush_cache(cpqary3p);
509 509
510 510 /* Undo cpqary3_attach */
511 511
512 512 cpqary3_cleanup(cpqary3p, CPQARY3_CLEAN_ALL);
513 513
514 514 return (DDI_SUCCESS);
515 515
516 516 }
517 517
518 518 /*
519 519 * Function : cpary3_ioctl
520 520 * Description : This routine services ioctl requests.
521 521 * Called By : kernel
522 522 * Parameters : Too many to list. Please look below !!!
523 523 * Return Values: 0 / EINVAL / EFAULT /
524 524 * [0 on normal successful completion of the ioctl
525 525 * request]
526 526 */
527 527 int
528 528 cpqary3_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
529 529 int *retvaluep)
530 530 {
531 531 minor_t cpqary3_minor_num;
532 532 cpqary3_t *cpqary3p;
533 533 int instance;
534 534
535 535 /*
536 536 * Get the soft state structure for this instance
537 537 * Return ENODEV if the structure does not exist.
538 538 */
539 539
540 540 /*
541 541 * minor() call used in cpqary3_ioctl() returns minor number of the
542 542 * device which are in the
543 543 * range 0-255. if the minor number of the device is greater than 255,
544 544 * data will get truncated. so we are now using getminor(),
545 545 * instead of minor()
546 546 */
547 547
548 548 if (EINVAL == (cpqary3_minor_num = getminor(dev))) {
549 549 *retvaluep = ENODEV;
550 550 return (*retvaluep);
551 551 }
552 552
553 553 /* HPQacucli Changes */
554 554
555 555 /* get instance */
556 556 instance = MINOR2INST(cpqary3_minor_num);
557 557
558 558 cpqary3p = (cpqary3_t *)ddi_get_soft_state(cpqary3_state, instance);
559 559
560 560 /* HPQacucli Changes */
561 561
562 562 if (!cpqary3p) {
563 563 *retvaluep = ENODEV;
564 564 return (*retvaluep);
565 565 }
566 566
567 567 /* HPQacucli Changes */
568 568
569 569 /* check which interface is being requested */
570 570 if (CPQARY3_MINOR2INTERFACE(cpqary3_minor_num) != CPQARY3_MINOR) {
571 571 /* defer to SCSA */
572 572 return (scsi_hba_ioctl(dev, cmd, arg, mode, credp, retvaluep));
573 573 }
574 574
575 575 /* HPQacucli Changes */
576 576
577 577 switch (cmd) {
578 578 case CPQARY3_IOCTL_DRIVER_INFO:
579 579 *retvaluep =
580 580 cpqary3_ioctl_driver_info(arg, mode);
581 581 break;
582 582
583 583 case CPQARY3_IOCTL_CTLR_INFO:
584 584 *retvaluep =
585 585 cpqary3_ioctl_ctlr_info(arg, cpqary3p, mode);
586 586 break;
587 587
588 588 case CPQARY3_IOCTL_BMIC_PASS:
589 589 *retvaluep =
590 590 cpqary3_ioctl_bmic_pass(arg, cpqary3p, mode);
591 591 break;
592 592
593 593 case CPQARY3_IOCTL_SCSI_PASS:
594 594 *retvaluep =
595 595 cpqary3_ioctl_scsi_pass(arg, cpqary3p, mode);
596 596 break;
597 597
598 598 default:
599 599 *retvaluep = EINVAL;
600 600 break;
601 601 }
602 602 return (*retvaluep);
603 603
604 604
605 605 }
606 606
607 607
608 608 /*
609 609 * Function : cqpary3_cleanup
610 610 * Description : This routine frees all allocated resources.
611 611 * Called By : kernel
612 612 * Parameters : per-controller, bit-map(stating what all to clean)
613 613 * Return Values: None
614 614 */
615 615 static void
616 616 cpqary3_cleanup(cpqary3_t *cpqary3p, uint32_t status)
617 617 {
618 618 int8_t node_name[10];
619 619 clock_t cpqary3_lbolt;
620 620 uint32_t targ;
621 621
622 622 ASSERT(cpqary3p != NULL);
623 623
624 624 /*
625 625 * Disable the NOE command
626 626 * Free the Command Memory Pool
627 627 * destroy all conditional variables
628 628 */
629 629
630 630 /*
631 631 * We have removed NOE functionality from the
632 632 * driver. So commenting the below piece of code
633 633 */
634 634
635 635 if (status & CPQARY3_NOE_INIT_DONE) {
636 636 if (CPQARY3_SUCCESS == cpqary3_disable_NOE_command(cpqary3p)) {
637 637 mutex_enter(&cpqary3p->hw_mutex);
638 638 cpqary3_lbolt = ddi_get_lbolt();
639 639 if (DDI_FAILURE ==
640 640 cv_timedwait_sig(&cpqary3p->cv_noe_wait,
641 641 &cpqary3p->hw_mutex,
642 642 cpqary3_lbolt + drv_usectohz(3000000))) {
643 643 cmn_err(CE_NOTE,
644 644 "CPQary3: Resume signal for disable NOE "
645 645 "command not received \n");
646 646 }
647 647 mutex_exit(&cpqary3p->hw_mutex);
648 648 }
649 649 }
650 650
651 651 /*
652 652 * Detach the device
653 653 * Free / Release / Destroy the following entities/resources:
654 654 * transport layer
655 655 * h/w & s/w interrupt handlers
656 656 * all mutex
657 657 * timeout handler
658 658 * target structure
659 659 * minor node
660 660 * soft state
661 661 * any register/memory mapping
662 662 */
663 663
664 664 if (status & CPQARY3_INTR_HDLR_SET)
665 665 ddi_remove_intr(cpqary3p->dip, 0, cpqary3p->hw_iblock_cookie);
666 666
667 667 if (status & CPQARY3_SW_INTR_HDLR_SET)
668 668 ddi_remove_softintr(cpqary3p->cpqary3_softintr_id);
669 669
670 670 if ((status & CPQARY3_TICK_TMOUT_REGD) && cpqary3p->tick_tmout_id) {
671 671 VERIFY(untimeout(cpqary3p->tick_tmout_id) >= 0);
672 672 cpqary3p->tick_tmout_id = NULL;
673 673 }
674 674
675 675 if (status & CPQARY3_CREATE_MINOR_NODE) {
676 676 (void) sprintf(node_name, "cpqary3%d", cpqary3p->instance);
677 677 ddi_remove_minor_node(cpqary3p->dip, node_name);
678 678 }
679 679
680 680 if (status & CPQARY3_HBA_TRAN_ATTACH_DONE)
681 681 (void) scsi_hba_detach(cpqary3p->dip);
682 682
683 683 if (status & CPQARY3_HBA_TRAN_ALLOC_DONE)
684 684 scsi_hba_tran_free(cpqary3p->hba_tran);
685 685
686 686 if (status & CPQARY3_CTLR_CONFIG_DONE) {
687 687 mutex_enter(&cpqary3p->hw_mutex);
688 688
689 689 cv_destroy(&cpqary3p->cv_abort_wait);
690 690 cv_destroy(&cpqary3p->cv_flushcache_wait);
691 691 cv_destroy(&cpqary3p->cv_noe_wait);
692 692 cv_destroy(&cpqary3p->cv_immediate_wait);
693 693 cv_destroy(&cpqary3p->cv_ioctl_wait);
694 694
695 695 for (targ = 0; targ < CPQARY3_MAX_TGT; targ++) {
696 696 if (cpqary3p->cpqary3_tgtp[targ] == NULL)
697 697 continue;
698 698 MEM_SFREE(cpqary3p->cpqary3_tgtp[targ],
699 699 sizeof (cpqary3_tgt_t));
700 700 }
701 701
702 702 mutex_exit(&cpqary3p->hw_mutex);
703 703
704 704 cpqary3_memfini(cpqary3p, CPQARY3_MEMLIST_DONE |
705 705 CPQARY3_PHYCTGS_DONE | CPQARY3_CMDMEM_DONE);
706 706 }
707 707
708 708 if (status & CPQARY3_SW_MUTEX_INIT_DONE)
709 709 mutex_destroy(&cpqary3p->sw_mutex);
710 710
711 711 if (status & CPQARY3_MUTEX_INIT_DONE)
712 712 mutex_destroy(&cpqary3p->hw_mutex);
713 713
714 714 /*
715 715 * If this flag is set, free all mapped registers
716 716 */
717 717 if (status & CPQARY3_MEM_MAPPED) {
718 718 if (cpqary3p->idr_handle)
719 719 ddi_regs_map_free(&cpqary3p->idr_handle);
720 720 if (cpqary3p->isr_handle)
721 721 ddi_regs_map_free(&cpqary3p->isr_handle);
722 722 if (cpqary3p->imr_handle)
723 723 ddi_regs_map_free(&cpqary3p->imr_handle);
724 724 if (cpqary3p->ipq_handle)
725 725 ddi_regs_map_free(&cpqary3p->ipq_handle);
726 726 if (cpqary3p->opq_handle)
727 727 ddi_regs_map_free(&cpqary3p->opq_handle);
728 728 if (cpqary3p->ct_handle)
729 729 ddi_regs_map_free(&cpqary3p->ct_handle);
730 730 }
731 731
732 732 if (status & CPQARY3_SOFTSTATE_ALLOC_DONE) {
733 733 ddi_soft_state_free(cpqary3_state,
734 734 ddi_get_instance(cpqary3p->dip));
735 735 }
736 736 }
737 737
738 738 /*
739 739 * Function : cpqary3_update_ctlrdetails
740 740 * Description : Performs Sanity check of the hw, Updates PCI Config
741 741 * Information, Verifies the supported board-id and
742 742 * Sets up a mapping for the Primary I2O Memory BAR and
743 743 * the Primary DRAM 1 BAR to access Host Interface
744 744 * registers and the Transport Configuration table.
745 745 * Called By : cpqary3_attach()
746 746 * Parameters : per-controller, bitmap (used for cleaning operations)
747 747 * Return Values: SUCCESS / FAILURE
748 748 * [Success / failure depending upon the outcome of all
749 749 * checks and mapping. If any of them fail, a failure is
750 750 * sent back]
751 751 */
752 752 static uint8_t
753 753 cpqary3_update_ctlrdetails(cpqary3_t *cpqary3p, uint32_t *cleanstatus)
754 754 {
755 755 int8_t retvalue;
756 756 uint8_t mem_bar0_set = 0;
757 757 uint8_t mem_64_bar0_set = 0;
758 758 uint8_t mem_bar1_set = 0;
759 759 uint8_t mem_64_bar1_set = 0;
760 760 int32_t reglen;
761 761 uint32_t *regp;
762 762 uint32_t mem_bar0 = 0;
763 763 uint32_t mem_64_bar0;
764 764 uint32_t mem_bar1 = 0;
765 765 uint32_t mem_64_bar1 = 0;
766 766 uint32_t ct_mem_bar = 0;
767 767 uint32_t ct_cfgmem_val = 0;
768 768 uint32_t ct_memoff_val = 0;
769 769 uint32_t ct_cfg_bar = 0;
770 770 uint32_t ct_mem_len = 0;
771 771 offset_t map_len = 0;
772 772 uint32_t regset_index;
773 773 ddi_acc_handle_t pci_handle;
774 774 uint32_t *ct_cfg_offset;
775 775 ddi_acc_handle_t ct_cfgoff_handle;
776 776 uint32_t *ct_mem_offset;
777 777 ddi_acc_handle_t ct_memoff_handle;
778 778
779 779 RETURN_FAILURE_IF_NULL(cpqary3p);
780 780
781 781 /*
782 782 * Check if the bus, or part of the bus that the device is installed
783 783 * on, permits the device to become a DMA master.
784 784 * If our device is not permitted to become master, return
785 785 */
786 786 if (ddi_slaveonly(cpqary3p->dip) == DDI_SUCCESS)
787 787 return (CPQARY3_FAILURE);
788 788
789 789 /*
790 790 * Get the HW Configuration
791 791 * Get Bus #, Dev # and Func # for this device
792 792 * Free the memory that regp points towards after the
793 793 * ddi_getlongprop() call
794 794 */
795 795 if (ddi_getlongprop(DDI_DEV_T_NONE, cpqary3p->dip, DDI_PROP_DONTPASS,
796 796 "reg", (caddr_t)®p, ®len) != DDI_PROP_SUCCESS)
797 797 return (CPQARY3_FAILURE);
798 798
799 799 cpqary3p->bus = PCI_REG_BUS_G(*regp);
800 800 cpqary3p->dev = PCI_REG_DEV_G(*regp);
801 801 cpqary3p->fun = PCI_REG_FUNC_G(*regp);
802 802
803 803 for (regset_index = 0; regset_index < reglen / 20; regset_index ++) {
804 804 if (PCI_REG_ADDR_G(*(regp + regset_index * 5)) == 0x2) {
805 805 if (!mem_bar0_set) {
806 806 mem_bar0 = regset_index;
807 807 mem_bar0_set = 1;
808 808 } else if (!mem_bar1_set) {
809 809 mem_bar1 = regset_index;
810 810 mem_bar1_set = 1;
811 811 }
812 812 }
813 813 }
814 814
815 815 mem_64_bar0 = mem_bar0;
816 816 mem_64_bar1 = mem_bar1;
817 817
818 818 for (regset_index = 0; regset_index < reglen / 20; regset_index ++) {
819 819 if (PCI_REG_ADDR_G(*(regp + regset_index * 5)) == 0x3) {
820 820 if (!mem_64_bar0_set) {
821 821 mem_64_bar0 = regset_index;
822 822 mem_64_bar0_set = 1;
823 823 } else if (!mem_64_bar1_set) {
824 824 mem_64_bar1 = regset_index;
825 825 mem_64_bar1_set = 1;
826 826 }
827 827 }
828 828 }
829 829
830 830 mem_bar0 = mem_64_bar0;
831 831 mem_bar1 = mem_64_bar1;
832 832
833 833 MEM_SFREE(regp, reglen);
834 834
835 835 /*
836 836 * Setup resources to access the Local PCI Bus
837 837 * If unsuccessful, return.
838 838 * Else, read the following from the PCI space:
839 839 * Sub-System Vendor ID
840 840 * Sub-System Device ID
841 841 * Interrupt Line
842 842 * Command Register
843 843 * Free the just allocated resources.
844 844 */
845 845 if (pci_config_setup(cpqary3p->dip, &pci_handle) != DDI_SUCCESS)
846 846 return (CPQARY3_FAILURE);
847 847
848 848 cpqary3p->irq = pci_config_get8(pci_handle, PCI_CONF_ILINE);
849 849 cpqary3p->board_id =
850 850 (pci_config_get16(pci_handle, PCI_CONF_SUBVENID) << 16)
851 851 | pci_config_get16(pci_handle, PCI_CONF_SUBSYSID);
852 852
853 853 pci_config_teardown(&pci_handle);
854 854
855 855 /*
856 856 * Verify Board Id
857 857 * If unsupported boards are detected, return.
858 858 * Update name for controller for driver use.
859 859 */
860 860 cpqary3p->bddef = cpqary3_bd_getbybid(cpqary3p->board_id);
861 861 if (cpqary3p->bddef == NULL) {
862 862 cmn_err(CE_WARN,
863 863 "CPQary3: <Bid 0x%X> Controller NOT Supported",
864 864 cpqary3p->board_id);
865 865 return (CPQARY3_FAILURE);
866 866 }
867 867 map_len = cpqary3p->bddef->bd_maplen;
868 868 (void) strcpy(cpqary3p->hba_name, cpqary3p->bddef->bd_dispname);
869 869
870 870 /*
871 871 * Set up a mapping for the following registers:
872 872 * Inbound Doorbell
873 873 * Outbound List Status
874 874 * Outbound Interrupt Mask
875 875 * Host Inbound Queue
876 876 * Host Outbound Queue
877 877 * Host Transport Configuration Table
878 878 * Mapping of the above has been done in that order.
879 879 */
880 880 retvalue = ddi_regs_map_setup(cpqary3p->dip,
881 881 mem_bar0, /* INDEX_PCI_BASE0, */
882 882 (caddr_t *)&cpqary3p->idr, (offset_t)I2O_IBDB_SET, map_len,
883 883 &cpqary3_dev_attributes, &cpqary3p->idr_handle);
884 884
885 885 if (retvalue != DDI_SUCCESS) {
886 886 if (DDI_REGS_ACC_CONFLICT == retvalue) {
887 887 cmn_err(CE_WARN,
888 888 "CPQary3 : Registers Mapping Conflict");
889 889 }
890 890 cmn_err(CE_WARN, "CPQary3 : Inbound Doorbell "
891 891 "Register Mapping Failed");
892 892 return (CPQARY3_FAILURE);
893 893 }
894 894
895 895 /* PERF */
896 896 retvalue = ddi_regs_map_setup(cpqary3p->dip,
897 897 mem_bar0, /* INDEX_PCI_BASE0, */
898 898 (caddr_t *)&cpqary3p->odr, (offset_t)I2O_OBDB_STATUS, map_len,
899 899 &cpqary3_dev_attributes, &cpqary3p->odr_handle);
900 900
901 901 if (retvalue != DDI_SUCCESS) {
902 902 if (DDI_REGS_ACC_CONFLICT == retvalue) {
903 903 cmn_err(CE_WARN,
904 904 "CPQary3 : Registers Mapping Conflict");
905 905 }
906 906 cmn_err(CE_WARN,
907 907 "CPQary3 : Outbound Doorbell Register Mapping Failed");
908 908 return (CPQARY3_FAILURE);
909 909 }
910 910
911 911 retvalue = ddi_regs_map_setup(cpqary3p->dip,
912 912 mem_bar0, /* INDEX_PCI_BASE0, */
913 913 (caddr_t *)&cpqary3p->odr_cl, (offset_t)I2O_OBDB_CLEAR, map_len,
914 914 &cpqary3_dev_attributes, &cpqary3p->odr_cl_handle);
915 915
916 916 if (retvalue != DDI_SUCCESS) {
917 917 if (DDI_REGS_ACC_CONFLICT == retvalue) {
918 918 cmn_err(CE_WARN,
919 919 "CPQary3 : Registers Mapping Conflict");
920 920 }
921 921 cmn_err(CE_WARN, "CPQary3 : Outbound Doorbell "
922 922 "Register Clear Mapping Failed");
923 923 return (CPQARY3_FAILURE);
924 924 }
925 925
926 926 /* LOCKUP CODE */
927 927 retvalue = ddi_regs_map_setup(cpqary3p->dip,
928 928 mem_bar0, /* INDEX_PCI_BASE0, */
929 929 (caddr_t *)&cpqary3p->spr0, (offset_t)I2O_CTLR_INIT, map_len,
930 930 &cpqary3_dev_attributes, &cpqary3p->spr0_handle);
931 931
932 932 if (retvalue != DDI_SUCCESS) {
933 933 if (DDI_REGS_ACC_CONFLICT == retvalue) {
934 934 cmn_err(CE_WARN,
935 935 "CPQary3 : Registers Mapping Conflict");
936 936 }
937 937 cmn_err(CE_WARN,
938 938 "CPQary3 : Scratch Pad register zero Mapping Failed");
939 939 return (CPQARY3_FAILURE);
940 940 }
941 941 /* LOCKUP CODE */
942 942 /* PERF */
943 943
944 944 *cleanstatus |= CPQARY3_MEM_MAPPED;
945 945
946 946 retvalue = ddi_regs_map_setup(cpqary3p->dip,
947 947 mem_bar0, /* INDEX_PCI_BASE0, */
948 948 (caddr_t *)&cpqary3p->isr, (offset_t)I2O_INT_STATUS, map_len,
949 949 &cpqary3_dev_attributes, &cpqary3p->isr_handle);
950 950
951 951 if (retvalue != DDI_SUCCESS) {
952 952 if (retvalue == DDI_REGS_ACC_CONFLICT) {
953 953 cmn_err(CE_WARN,
954 954 "CPQary3 : Registers Mapping Conflict");
955 955 }
956 956 cmn_err(CE_WARN,
957 957 "CPQary3 : Interrupt Status Register Mapping Failed");
958 958 return (CPQARY3_FAILURE);
959 959 }
960 960
961 961 retvalue = ddi_regs_map_setup(cpqary3p->dip,
962 962 mem_bar0, /* INDEX_PCI_BASE0, */
963 963 (caddr_t *)&cpqary3p->imr, (offset_t)I2O_INT_MASK, map_len,
964 964 &cpqary3_dev_attributes, &cpqary3p->imr_handle);
965 965
966 966 if (retvalue != DDI_SUCCESS) {
967 967 if (retvalue == DDI_REGS_ACC_CONFLICT) {
968 968 cmn_err(CE_WARN,
969 969 "CPQary3 : Registers Mapping Conflict");
970 970 }
971 971 cmn_err(CE_WARN,
972 972 "CPQary3 : Interrupt Mask Register Mapping Failed");
973 973 return (CPQARY3_FAILURE);
974 974 }
975 975
976 976 retvalue = ddi_regs_map_setup(cpqary3p->dip,
977 977 mem_bar0, /* INDEX_PCI_BASE0, */
978 978 (caddr_t *)&cpqary3p->ipq, (offset_t)I2O_IBPOST_Q, map_len,
979 979 &cpqary3_dev_attributes, &cpqary3p->ipq_handle);
980 980
981 981 if (retvalue != DDI_SUCCESS) {
982 982 if (retvalue == DDI_REGS_ACC_CONFLICT) {
983 983 cmn_err(CE_WARN,
984 984 "CPQary3 : Registers Mapping Conflict");
985 985 }
986 986 cmn_err(CE_WARN,
987 987 "CPQary3 : Inbound Queue Register Mapping Failed");
988 988 return (CPQARY3_FAILURE);
989 989 }
990 990
991 991 retvalue = ddi_regs_map_setup(cpqary3p->dip,
992 992 mem_bar0, /* INDEX_PCI_BASE0, */ (caddr_t *)&cpqary3p->opq,
993 993 (offset_t)I2O_OBPOST_Q, map_len, &cpqary3_dev_attributes,
994 994 &cpqary3p->opq_handle);
995 995
996 996 if (retvalue != DDI_SUCCESS) {
997 997 if (retvalue == DDI_REGS_ACC_CONFLICT) {
998 998 cmn_err(CE_WARN,
999 999 "CPQary3 : Registers Mapping Conflict");
1000 1000 }
1001 1001 cmn_err(CE_WARN, "CPQary3 : Outbound Post Queue "
1002 1002 "Register Mapping Failed");
1003 1003 return (CPQARY3_FAILURE);
1004 1004 }
1005 1005
1006 1006
1007 1007 /*
1008 1008 * The config offset and memory offset have to be obtained in order to
1009 1009 * locate the config table.
1010 1010 */
1011 1011 retvalue = ddi_regs_map_setup(cpqary3p->dip,
1012 1012 mem_bar0, /* INDEX_PCI_BASE0, */ (caddr_t *)&ct_cfg_offset,
1013 1013 (offset_t)CT_CFG_OFFSET, map_len, &cpqary3_dev_attributes,
1014 1014 &ct_cfgoff_handle);
1015 1015
1016 1016 if (retvalue != DDI_SUCCESS) {
1017 1017 if (retvalue == DDI_REGS_ACC_CONFLICT) {
1018 1018 cmn_err(CE_WARN,
1019 1019 "CPQary3 : Registers Mapping Conflict");
1020 1020 }
1021 1021 cmn_err(CE_WARN, "CPQary3 : Configuration Table "
1022 1022 "Register Mapping Failed");
1023 1023 return (CPQARY3_FAILURE);
1024 1024 }
1025 1025
1026 1026 retvalue = ddi_regs_map_setup(cpqary3p->dip,
1027 1027 mem_bar0, /* INDEX_PCI_BASE0, */
1028 1028 (caddr_t *)&ct_mem_offset, (offset_t)CT_MEM_OFFSET, map_len,
1029 1029 &cpqary3_dev_attributes, &ct_memoff_handle);
1030 1030
1031 1031 if (retvalue != DDI_SUCCESS) {
1032 1032 if (retvalue == DDI_REGS_ACC_CONFLICT) {
1033 1033 cmn_err(CE_WARN,
1034 1034 "CPQary3 : Registers Mapping Conflict");
1035 1035 }
1036 1036 cmn_err(CE_WARN, "CPQary3 : Configuration Table "
1037 1037 "Register Mapping Failed");
1038 1038 return (CPQARY3_FAILURE);
1039 1039 }
1040 1040
1041 1041 ct_cfgmem_val = (uint32_t)ddi_get32(ct_cfgoff_handle, ct_cfg_offset);
1042 1042 ct_memoff_val = (uint32_t)ddi_get32(ct_memoff_handle, ct_mem_offset);
1043 1043
1044 1044 ddi_regs_map_free(&ct_cfgoff_handle);
1045 1045 ddi_regs_map_free(&ct_memoff_handle);
1046 1046
1047 1047 ct_cfg_bar = (ct_cfgmem_val & 0x0000ffff);
1048 1048 ct_mem_len = (ct_cfgmem_val & 0xffff0000);
1049 1049 ct_mem_len = (ct_mem_len >> 16);
1050 1050
1051 1051 if (ct_cfg_bar == 0x10) {
1052 1052 if (ct_mem_len) {
1053 1053 ct_mem_bar = mem_64_bar0;
1054 1054 } else {
1055 1055 ct_mem_bar = mem_bar0;
1056 1056 }
1057 1057
1058 1058 } else if (ct_cfg_bar == 0x14) {
1059 1059 if (ct_mem_len) {
1060 1060 ct_mem_bar = mem_64_bar1;
1061 1061 } else {
1062 1062 ct_mem_bar = mem_bar1;
1063 1063 }
1064 1064 } else {
1065 1065 return (CPQARY3_FAILURE);
1066 1066 }
1067 1067
1068 1068
1069 1069 /*
1070 1070 * The Configuration Table(CT) shall be mapped in the form of a
1071 1071 * structure since several members in the CT need to be accessed
1072 1072 * to read and write.
1073 1073 */
1074 1074 retvalue = ddi_regs_map_setup(cpqary3p->dip,
1075 1075 ct_mem_bar, /* INDEX_PCI_BASE0/1, */
1076 1076 (caddr_t *)&cpqary3p->ct, (offset_t)ct_memoff_val,
1077 1077 sizeof (CfgTable_t), &cpqary3_dev_attributes, &cpqary3p->ct_handle);
1078 1078
1079 1079 if (retvalue != DDI_SUCCESS) {
1080 1080 if (retvalue == DDI_REGS_ACC_CONFLICT) {
1081 1081 cmn_err(CE_WARN,
1082 1082 "CPQary3 : Registers Mapping Conflict");
1083 1083 }
1084 1084 cmn_err(CE_WARN, "CPQary3 : Configuration Table "
1085 1085 "Register Mapping Failed");
1086 1086 return (CPQARY3_FAILURE);
1087 1087 }
1088 1088
1089 1089 /* PERF */
1090 1090
1091 1091 retvalue = ddi_regs_map_setup(cpqary3p->dip,
1092 1092 ct_mem_bar, /* INDEX_PCI_BASE0/1, */
1093 1093 (caddr_t *)&cpqary3p->cp,
1094 1094 (offset_t)(ct_memoff_val + cpqary3p->ct->TransportMethodOffset),
1095 1095 sizeof (CfgTrans_Perf_t), &cpqary3_dev_attributes,
1096 1096 &cpqary3p->cp_handle);
1097 1097
1098 1098 if (retvalue != DDI_SUCCESS) {
1099 1099 if (retvalue == DDI_REGS_ACC_CONFLICT)
1100 1100 cmn_err(CE_WARN,
1101 1101 "CPQary3 : Registers Mapping Conflict");
1102 1102 cmn_err(CE_WARN, "CPQary3 : Performant Transport Method Table "
1103 1103 "Mapping Failed");
1104 1104 return (CPQARY3_FAILURE);
1105 1105 }
1106 1106
1107 1107 /* PERF */
1108 1108
1109 1109 return (CPQARY3_SUCCESS);
1110 1110 }
↓ open down ↓ |
932 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX