FreeBSD/Linux Kernel Cross Reference
sys/dev/ic/mpt.c
1 /* $NetBSD: mpt.c,v 1.4 2003/11/02 11:07:45 wiz Exp $ */
2
3 /*
4 * Copyright (c) 2000, 2001 by Greg Ansley
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice immediately at the beginning of the file, without modification,
11 * this list of conditions, and the following disclaimer.
12 * 2. The name of the author may not be used to endorse or promote products
13 * derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
19 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27 /*
28 * Additional Copyright (c) 2002 by Matthew Jacob under same license.
29 */
30
31 /*
32 * mpt.c:
33 *
34 * Generic routines for LSI Fusion adapters.
35 *
36 * Adapted from the FreeBSD "mpt" driver by Jason R. Thorpe for
37 * Wasabi Systems, Inc.
38 */
39
40 #include <sys/cdefs.h>
41 __KERNEL_RCSID(0, "$NetBSD: mpt.c,v 1.4 2003/11/02 11:07:45 wiz Exp $");
42
43 #include <dev/ic/mpt.h>
44
45 #define MPT_MAX_TRYS 3
46 #define MPT_MAX_WAIT 300000
47
48 static int maxwait_ack = 0;
49 static int maxwait_int = 0;
50 static int maxwait_state = 0;
51
52 static __inline u_int32_t
53 mpt_rd_db(mpt_softc_t *mpt)
54 {
55 return mpt_read(mpt, MPT_OFFSET_DOORBELL);
56 }
57
58 static __inline u_int32_t
59 mpt_rd_intr(mpt_softc_t *mpt)
60 {
61 return mpt_read(mpt, MPT_OFFSET_INTR_STATUS);
62 }
63
64 /* Busy wait for a door bell to be read by IOC */
65 static int
66 mpt_wait_db_ack(mpt_softc_t *mpt)
67 {
68 int i;
69 for (i=0; i < MPT_MAX_WAIT; i++) {
70 if (!MPT_DB_IS_BUSY(mpt_rd_intr(mpt))) {
71 maxwait_ack = i > maxwait_ack ? i : maxwait_ack;
72 return MPT_OK;
73 }
74
75 DELAY(100);
76 }
77 return MPT_FAIL;
78 }
79
80 /* Busy wait for a door bell interrupt */
81 static int
82 mpt_wait_db_int(mpt_softc_t *mpt)
83 {
84 int i;
85 for (i=0; i < MPT_MAX_WAIT; i++) {
86 if (MPT_DB_INTR(mpt_rd_intr(mpt))) {
87 maxwait_int = i > maxwait_int ? i : maxwait_int;
88 return MPT_OK;
89 }
90 DELAY(100);
91 }
92 return MPT_FAIL;
93 }
94
95 /* Wait for IOC to transition to a give state */
96 void
97 mpt_check_doorbell(mpt_softc_t *mpt)
98 {
99 u_int32_t db = mpt_rd_db(mpt);
100 if (MPT_STATE(db) != MPT_DB_STATE_RUNNING) {
101 mpt_prt(mpt, "Device not running");
102 mpt_print_db(db);
103 }
104 }
105
106 /* Wait for IOC to transition to a give state */
107 static int
108 mpt_wait_state(mpt_softc_t *mpt, enum DB_STATE_BITS state)
109 {
110 int i;
111
112 for (i = 0; i < MPT_MAX_WAIT; i++) {
113 u_int32_t db = mpt_rd_db(mpt);
114 if (MPT_STATE(db) == state) {
115 maxwait_state = i > maxwait_state ? i : maxwait_state;
116 return (MPT_OK);
117 }
118 DELAY(100);
119 }
120 return (MPT_FAIL);
121 }
122
123
124 /* Issue the reset COMMAND to the IOC */
125 int
126 mpt_soft_reset(mpt_softc_t *mpt)
127 {
128 if (mpt->verbose) {
129 mpt_prt(mpt, "soft reset");
130 }
131
132 /* Have to use hard reset if we are not in Running state */
133 if (MPT_STATE(mpt_rd_db(mpt)) != MPT_DB_STATE_RUNNING) {
134 mpt_prt(mpt, "soft reset failed: device not running");
135 return MPT_FAIL;
136 }
137
138 /* If door bell is in use we don't have a chance of getting
139 * a word in since the IOC probably crashed in message
140 * processing. So don't waste our time.
141 */
142 if (MPT_DB_IS_IN_USE(mpt_rd_db(mpt))) {
143 mpt_prt(mpt, "soft reset failed: doorbell wedged");
144 return MPT_FAIL;
145 }
146
147 /* Send the reset request to the IOC */
148 mpt_write(mpt, MPT_OFFSET_DOORBELL,
149 MPI_FUNCTION_IOC_MESSAGE_UNIT_RESET << MPI_DOORBELL_FUNCTION_SHIFT);
150 if (mpt_wait_db_ack(mpt) != MPT_OK) {
151 mpt_prt(mpt, "soft reset failed: ack timeout");
152 return MPT_FAIL;
153 }
154
155 /* Wait for the IOC to reload and come out of reset state */
156 if (mpt_wait_state(mpt, MPT_DB_STATE_READY) != MPT_OK) {
157 mpt_prt(mpt, "soft reset failed: device did not start running");
158 return MPT_FAIL;
159 }
160
161 return MPT_OK;
162 }
163
164 /* This is a magic diagnostic reset that resets all the ARM
165 * processors in the chip.
166 */
167 void
168 mpt_hard_reset(mpt_softc_t *mpt)
169 {
170 /* This extra read comes for the Linux source
171 * released by LSI. It's function is undocumented!
172 */
173 if (mpt->verbose) {
174 mpt_prt(mpt, "hard reset");
175 }
176 mpt_read(mpt, MPT_OFFSET_FUBAR);
177
178 /* Enable diagnostic registers */
179 mpt_write(mpt, MPT_OFFSET_SEQUENCE, MPT_DIAG_SEQUENCE_1);
180 mpt_write(mpt, MPT_OFFSET_SEQUENCE, MPT_DIAG_SEQUENCE_2);
181 mpt_write(mpt, MPT_OFFSET_SEQUENCE, MPT_DIAG_SEQUENCE_3);
182 mpt_write(mpt, MPT_OFFSET_SEQUENCE, MPT_DIAG_SEQUENCE_4);
183 mpt_write(mpt, MPT_OFFSET_SEQUENCE, MPT_DIAG_SEQUENCE_5);
184
185 /* Diag. port is now active so we can now hit the reset bit */
186 mpt_write(mpt, MPT_OFFSET_DIAGNOSTIC, MPT_DIAG_RESET_IOC);
187
188 DELAY(10000);
189
190 /* Disable Diagnostic Register */
191 mpt_write(mpt, MPT_OFFSET_SEQUENCE, 0xFF);
192
193 /* Restore the config register values */
194 /* Hard resets are known to screw up the BAR for diagnostic
195 memory accesses (Mem1). */
196 mpt_set_config_regs(mpt);
197 if (mpt->mpt2 != NULL) {
198 mpt_set_config_regs(mpt->mpt2);
199 }
200
201 /* Note that if there is no valid firmware to run, the doorbell will
202 remain in the reset state (0x00000000) */
203 }
204
205 /*
206 * Reset the IOC when needed. Try software command first then if needed
207 * poke at the magic diagnostic reset. Note that a hard reset resets
208 * *both* IOCs on dual function chips (FC929 && LSI1030) as well as
209 * fouls up the PCI configuration registers.
210 */
211 int
212 mpt_reset(mpt_softc_t *mpt)
213 {
214 int ret;
215
216 /* Try a soft reset */
217 if ((ret = mpt_soft_reset(mpt)) != MPT_OK) {
218 /* Failed; do a hard reset */
219 mpt_hard_reset(mpt);
220
221 /* Wait for the IOC to reload and come out of reset state */
222 ret = mpt_wait_state(mpt, MPT_DB_STATE_READY);
223 if (ret != MPT_OK) {
224 mpt_prt(mpt, "failed to reset device");
225 }
226 }
227
228 return ret;
229 }
230
231 /* Return a command buffer to the free queue */
232 void
233 mpt_free_request(mpt_softc_t *mpt, request_t *req)
234 {
235 if (req == NULL || req != &mpt->request_pool[req->index]) {
236 panic("mpt_free_request bad req ptr\n");
237 return;
238 }
239 req->sequence = 0;
240 req->xfer = NULL;
241 req->debug = REQ_FREE;
242 SLIST_INSERT_HEAD(&mpt->request_free_list, req, link);
243 }
244
245 /* Get a command buffer from the free queue */
246 request_t *
247 mpt_get_request(mpt_softc_t *mpt)
248 {
249 request_t *req;
250 req = SLIST_FIRST(&mpt->request_free_list);
251 if (req != NULL) {
252 if (req != &mpt->request_pool[req->index]) {
253 panic("mpt_get_request: corrupted request free list\n");
254 }
255 if (req->xfer != NULL) {
256 panic("mpt_get_request: corrupted request free list (xfer)\n");
257 }
258 SLIST_REMOVE_HEAD(&mpt->request_free_list, link);
259 req->debug = REQ_IN_PROGRESS;
260 }
261 return req;
262 }
263
264 /* Pass the command to the IOC */
265 void
266 mpt_send_cmd(mpt_softc_t *mpt, request_t *req)
267 {
268 req->sequence = mpt->sequence++;
269 if (mpt->verbose > 1) {
270 u_int32_t *pReq;
271 pReq = req->req_vbuf;
272 mpt_prt(mpt, "Send Request %d (0x%x):",
273 req->index, req->req_pbuf);
274 mpt_prt(mpt, "%08x %08x %08x %08x",
275 pReq[0], pReq[1], pReq[2], pReq[3]);
276 mpt_prt(mpt, "%08x %08x %08x %08x",
277 pReq[4], pReq[5], pReq[6], pReq[7]);
278 mpt_prt(mpt, "%08x %08x %08x %08x",
279 pReq[8], pReq[9], pReq[10], pReq[11]);
280 mpt_prt(mpt, "%08x %08x %08x %08x",
281 pReq[12], pReq[13], pReq[14], pReq[15]);
282 }
283 MPT_SYNC_REQ(mpt, req, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
284 req->debug = REQ_ON_CHIP;
285 mpt_write(mpt, MPT_OFFSET_REQUEST_Q, (u_int32_t) req->req_pbuf);
286 }
287
288 /*
289 * Give the reply buffer back to the IOC after we have
290 * finished processing it.
291 */
292 void
293 mpt_free_reply(mpt_softc_t *mpt, u_int32_t ptr)
294 {
295 mpt_write(mpt, MPT_OFFSET_REPLY_Q, ptr);
296 }
297
298 /* Get a reply from the IOC */
299 u_int32_t
300 mpt_pop_reply_queue(mpt_softc_t *mpt)
301 {
302 return mpt_read(mpt, MPT_OFFSET_REPLY_Q);
303 }
304
305 /*
306 * Send a command to the IOC via the handshake register.
307 *
308 * Only done at initialization time and for certain unusual
309 * commands such as device/bus reset as specified by LSI.
310 */
311 int
312 mpt_send_handshake_cmd(mpt_softc_t *mpt, size_t len, void *cmd)
313 {
314 int i;
315 u_int32_t data, *data32;
316
317 /* Check condition of the IOC */
318 data = mpt_rd_db(mpt);
319 if (((MPT_STATE(data) != MPT_DB_STATE_READY) &&
320 (MPT_STATE(data) != MPT_DB_STATE_RUNNING) &&
321 (MPT_STATE(data) != MPT_DB_STATE_FAULT)) ||
322 ( MPT_DB_IS_IN_USE(data) )) {
323 mpt_prt(mpt, "handshake aborted due to invalid doorbell state");
324 mpt_print_db(data);
325 return(EBUSY);
326 }
327
328 /* We move things in 32 bit chunks */
329 len = (len + 3) >> 2;
330 data32 = cmd;
331
332 /* Clear any left over pending doorbell interrupts */
333 if (MPT_DB_INTR(mpt_rd_intr(mpt)))
334 mpt_write(mpt, MPT_OFFSET_INTR_STATUS, 0);
335
336 /*
337 * Tell the handshake reg. we are going to send a command
338 * and how long it is going to be.
339 */
340 data = (MPI_FUNCTION_HANDSHAKE << MPI_DOORBELL_FUNCTION_SHIFT) |
341 (len << MPI_DOORBELL_ADD_DWORDS_SHIFT);
342 mpt_write(mpt, MPT_OFFSET_DOORBELL, data);
343
344 /* Wait for the chip to notice */
345 if (mpt_wait_db_int(mpt) != MPT_OK) {
346 mpt_prt(mpt, "mpt_send_handshake_cmd timeout1");
347 return ETIMEDOUT;
348 }
349
350 /* Clear the interrupt */
351 mpt_write(mpt, MPT_OFFSET_INTR_STATUS, 0);
352
353 if (mpt_wait_db_ack(mpt) != MPT_OK) {
354 mpt_prt(mpt, "mpt_send_handshake_cmd timeout2");
355 return ETIMEDOUT;
356 }
357
358 /* Send the command */
359 for (i = 0; i < len; i++) {
360 mpt_write(mpt, MPT_OFFSET_DOORBELL, *data32++);
361 if (mpt_wait_db_ack(mpt) != MPT_OK) {
362 mpt_prt(mpt,
363 "mpt_send_handshake_cmd timeout! index = %d", i);
364 return ETIMEDOUT;
365 }
366 }
367 return MPT_OK;
368 }
369
370 /* Get the response from the handshake register */
371 int
372 mpt_recv_handshake_reply(mpt_softc_t *mpt, size_t reply_len, void *reply)
373 {
374 int left, reply_left;
375 u_int16_t *data16;
376 MSG_DEFAULT_REPLY *hdr;
377
378 /* We move things out in 16 bit chunks */
379 reply_len >>= 1;
380 data16 = (u_int16_t *)reply;
381
382 hdr = (MSG_DEFAULT_REPLY *)reply;
383
384 /* Get first word */
385 if (mpt_wait_db_int(mpt) != MPT_OK) {
386 mpt_prt(mpt, "mpt_recv_handshake_cmd timeout1");
387 return ETIMEDOUT;
388 }
389 *data16++ = mpt_read(mpt, MPT_OFFSET_DOORBELL) & MPT_DB_DATA_MASK;
390 mpt_write(mpt, MPT_OFFSET_INTR_STATUS, 0);
391
392 /* Get Second Word */
393 if (mpt_wait_db_int(mpt) != MPT_OK) {
394 mpt_prt(mpt, "mpt_recv_handshake_cmd timeout2");
395 return ETIMEDOUT;
396 }
397 *data16++ = mpt_read(mpt, MPT_OFFSET_DOORBELL) & MPT_DB_DATA_MASK;
398 mpt_write(mpt, MPT_OFFSET_INTR_STATUS, 0);
399
400 /* With the second word, we can now look at the length */
401 if (mpt->verbose > 1 && ((reply_len >> 1) != hdr->MsgLength)) {
402 mpt_prt(mpt, "reply length does not match message length: "
403 "got 0x%02x, expected 0x%02x",
404 hdr->MsgLength << 2, reply_len << 1);
405 }
406
407 /* Get rest of the reply; but don't overflow the provided buffer */
408 left = (hdr->MsgLength << 1) - 2;
409 reply_left = reply_len - 2;
410 while (left--) {
411 u_int16_t datum;
412
413 if (mpt_wait_db_int(mpt) != MPT_OK) {
414 mpt_prt(mpt, "mpt_recv_handshake_cmd timeout3");
415 return ETIMEDOUT;
416 }
417 datum = mpt_read(mpt, MPT_OFFSET_DOORBELL);
418
419 if (reply_left-- > 0)
420 *data16++ = datum & MPT_DB_DATA_MASK;
421
422 mpt_write(mpt, MPT_OFFSET_INTR_STATUS, 0);
423 }
424
425 /* One more wait & clear at the end */
426 if (mpt_wait_db_int(mpt) != MPT_OK) {
427 mpt_prt(mpt, "mpt_recv_handshake_cmd timeout4");
428 return ETIMEDOUT;
429 }
430 mpt_write(mpt, MPT_OFFSET_INTR_STATUS, 0);
431
432 if ((hdr->IOCStatus & MPI_IOCSTATUS_MASK) != MPI_IOCSTATUS_SUCCESS) {
433 if (mpt->verbose > 1)
434 mpt_print_reply(hdr);
435 return (MPT_FAIL | hdr->IOCStatus);
436 }
437
438 return (0);
439 }
440
441 static int
442 mpt_get_iocfacts(mpt_softc_t *mpt, MSG_IOC_FACTS_REPLY *freplp)
443 {
444 MSG_IOC_FACTS f_req;
445 int error;
446
447 bzero(&f_req, sizeof f_req);
448 f_req.Function = MPI_FUNCTION_IOC_FACTS;
449 f_req.MsgContext = 0x12071942;
450 error = mpt_send_handshake_cmd(mpt, sizeof f_req, &f_req);
451 if (error)
452 return(error);
453 error = mpt_recv_handshake_reply(mpt, sizeof (*freplp), freplp);
454 return (error);
455 }
456
457 static int
458 mpt_get_portfacts(mpt_softc_t *mpt, MSG_PORT_FACTS_REPLY *freplp)
459 {
460 MSG_PORT_FACTS f_req;
461 int error;
462
463 /* XXX: Only getting PORT FACTS for Port 0 */
464 bzero(&f_req, sizeof f_req);
465 f_req.Function = MPI_FUNCTION_PORT_FACTS;
466 f_req.MsgContext = 0x12071943;
467 error = mpt_send_handshake_cmd(mpt, sizeof f_req, &f_req);
468 if (error)
469 return(error);
470 error = mpt_recv_handshake_reply(mpt, sizeof (*freplp), freplp);
471 return (error);
472 }
473
474 /*
475 * Send the initialization request. This is where we specify how many
476 * SCSI busses and how many devices per bus we wish to emulate.
477 * This is also the command that specifies the max size of the reply
478 * frames from the IOC that we will be allocating.
479 */
480 static int
481 mpt_send_ioc_init(mpt_softc_t *mpt, u_int32_t who)
482 {
483 int error = 0;
484 MSG_IOC_INIT init;
485 MSG_IOC_INIT_REPLY reply;
486
487 bzero(&init, sizeof init);
488 init.WhoInit = who;
489 init.Function = MPI_FUNCTION_IOC_INIT;
490 if (mpt->is_fc) {
491 init.MaxDevices = 255;
492 } else {
493 init.MaxDevices = 16;
494 }
495 init.MaxBuses = 1;
496 init.ReplyFrameSize = MPT_REPLY_SIZE;
497 init.MsgContext = 0x12071941;
498
499 if ((error = mpt_send_handshake_cmd(mpt, sizeof init, &init)) != 0) {
500 return(error);
501 }
502
503 error = mpt_recv_handshake_reply(mpt, sizeof reply, &reply);
504 return (error);
505 }
506
507
508 /*
509 * Utiltity routine to read configuration headers and pages
510 */
511
512 static int
513 mpt_read_cfg_header(mpt_softc_t *, int, int, int, fCONFIG_PAGE_HEADER *);
514
515 static int
516 mpt_read_cfg_header(mpt_softc_t *mpt, int PageType, int PageNumber,
517 int PageAddress, fCONFIG_PAGE_HEADER *rslt)
518 {
519 int count;
520 request_t *req;
521 MSG_CONFIG *cfgp;
522 MSG_CONFIG_REPLY *reply;
523
524 req = mpt_get_request(mpt);
525
526 cfgp = req->req_vbuf;
527 bzero(cfgp, sizeof *cfgp);
528
529 cfgp->Action = MPI_CONFIG_ACTION_PAGE_HEADER;
530 cfgp->Function = MPI_FUNCTION_CONFIG;
531 cfgp->Header.PageNumber = (U8) PageNumber;
532 cfgp->Header.PageType = (U8) PageType;
533 cfgp->PageAddress = PageAddress;
534 MPI_pSGE_SET_FLAGS(((SGE_SIMPLE32 *) &cfgp->PageBufferSGE),
535 (MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
536 MPI_SGE_FLAGS_SIMPLE_ELEMENT | MPI_SGE_FLAGS_END_OF_LIST));
537 cfgp->MsgContext = req->index | 0x80000000;
538
539 mpt_check_doorbell(mpt);
540 mpt_send_cmd(mpt, req);
541 count = 0;
542 do {
543 DELAY(500);
544 mpt_intr(mpt);
545 if (++count == 1000) {
546 mpt_prt(mpt, "read_cfg_header timed out");
547 return (-1);
548 }
549 } while (req->debug == REQ_ON_CHIP);
550
551 reply = (MSG_CONFIG_REPLY *) MPT_REPLY_PTOV(mpt, req->sequence);
552 if ((reply->IOCStatus & MPI_IOCSTATUS_MASK) != MPI_IOCSTATUS_SUCCESS) {
553 mpt_prt(mpt, "mpt_read_cfg_header: Config Info Status %x",
554 reply->IOCStatus);
555 mpt_free_reply(mpt, (req->sequence << 1));
556 return (-1);
557 }
558 bcopy(&reply->Header, rslt, sizeof (fCONFIG_PAGE_HEADER));
559 mpt_free_reply(mpt, (req->sequence << 1));
560 mpt_free_request(mpt, req);
561 return (0);
562 }
563
564 #define CFG_DATA_OFF 128
565
566 int
567 mpt_read_cfg_page(mpt_softc_t *mpt, int PageAddress, fCONFIG_PAGE_HEADER *hdr)
568 {
569 int count;
570 request_t *req;
571 SGE_SIMPLE32 *se;
572 MSG_CONFIG *cfgp;
573 size_t amt;
574 MSG_CONFIG_REPLY *reply;
575
576 req = mpt_get_request(mpt);
577
578 cfgp = req->req_vbuf;
579 bzero(cfgp, MPT_REQUEST_AREA);
580 cfgp->Action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT;
581 cfgp->Function = MPI_FUNCTION_CONFIG;
582 cfgp->Header = *hdr;
583 amt = (cfgp->Header.PageLength * sizeof (u_int32_t));
584 cfgp->Header.PageType &= MPI_CONFIG_PAGETYPE_MASK;
585 cfgp->PageAddress = PageAddress;
586 se = (SGE_SIMPLE32 *) &cfgp->PageBufferSGE;
587 se->Address = req->req_pbuf + CFG_DATA_OFF;
588 MPI_pSGE_SET_LENGTH(se, amt);
589 MPI_pSGE_SET_FLAGS(se, (MPI_SGE_FLAGS_SIMPLE_ELEMENT |
590 MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
591 MPI_SGE_FLAGS_END_OF_LIST));
592
593 cfgp->MsgContext = req->index | 0x80000000;
594
595 mpt_check_doorbell(mpt);
596 mpt_send_cmd(mpt, req);
597 count = 0;
598 do {
599 DELAY(500);
600 mpt_intr(mpt);
601 if (++count == 1000) {
602 mpt_prt(mpt, "read_cfg_page timed out");
603 return (-1);
604 }
605 } while (req->debug == REQ_ON_CHIP);
606
607 reply = (MSG_CONFIG_REPLY *) MPT_REPLY_PTOV(mpt, req->sequence);
608 if ((reply->IOCStatus & MPI_IOCSTATUS_MASK) != MPI_IOCSTATUS_SUCCESS) {
609 mpt_prt(mpt, "mpt_read_cfg_page: Config Info Status %x",
610 reply->IOCStatus);
611 mpt_free_reply(mpt, (req->sequence << 1));
612 return (-1);
613 }
614 mpt_free_reply(mpt, (req->sequence << 1));
615 #if 0 /* XXXJRT */
616 bus_dmamap_sync(mpt->request_dmat, mpt->request_dmap,
617 BUS_DMASYNC_POSTREAD);
618 #endif
619 if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_PORT &&
620 cfgp->Header.PageNumber == 0) {
621 amt = sizeof (fCONFIG_PAGE_SCSI_PORT_0);
622 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_PORT &&
623 cfgp->Header.PageNumber == 1) {
624 amt = sizeof (fCONFIG_PAGE_SCSI_PORT_1);
625 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_PORT &&
626 cfgp->Header.PageNumber == 2) {
627 amt = sizeof (fCONFIG_PAGE_SCSI_PORT_2);
628 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_DEVICE &&
629 cfgp->Header.PageNumber == 0) {
630 amt = sizeof (fCONFIG_PAGE_SCSI_DEVICE_0);
631 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_DEVICE &&
632 cfgp->Header.PageNumber == 1) {
633 amt = sizeof (fCONFIG_PAGE_SCSI_DEVICE_1);
634 }
635 bcopy(((caddr_t)req->req_vbuf)+CFG_DATA_OFF, hdr, amt);
636 mpt_free_request(mpt, req);
637 return (0);
638 }
639
640 int
641 mpt_write_cfg_page(mpt_softc_t *mpt, int PageAddress, fCONFIG_PAGE_HEADER *hdr)
642 {
643 int count, hdr_attr;
644 request_t *req;
645 SGE_SIMPLE32 *se;
646 MSG_CONFIG *cfgp;
647 size_t amt;
648 MSG_CONFIG_REPLY *reply;
649
650 req = mpt_get_request(mpt);
651
652 cfgp = req->req_vbuf;
653 bzero(cfgp, sizeof *cfgp);
654
655 hdr_attr = hdr->PageType & MPI_CONFIG_PAGEATTR_MASK;
656 if (hdr_attr != MPI_CONFIG_PAGEATTR_CHANGEABLE &&
657 hdr_attr != MPI_CONFIG_PAGEATTR_PERSISTENT) {
658 mpt_prt(mpt, "page type 0x%x not changeable",
659 hdr->PageType & MPI_CONFIG_PAGETYPE_MASK);
660 return (-1);
661 }
662 hdr->PageType &= MPI_CONFIG_PAGETYPE_MASK;
663
664 cfgp->Action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT;
665 cfgp->Function = MPI_FUNCTION_CONFIG;
666 cfgp->Header = *hdr;
667 amt = (cfgp->Header.PageLength * sizeof (u_int32_t));
668 cfgp->PageAddress = PageAddress;
669
670 se = (SGE_SIMPLE32 *) &cfgp->PageBufferSGE;
671 se->Address = req->req_pbuf + CFG_DATA_OFF;
672 MPI_pSGE_SET_LENGTH(se, amt);
673 MPI_pSGE_SET_FLAGS(se, (MPI_SGE_FLAGS_SIMPLE_ELEMENT |
674 MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
675 MPI_SGE_FLAGS_END_OF_LIST | MPI_SGE_FLAGS_HOST_TO_IOC));
676
677 cfgp->MsgContext = req->index | 0x80000000;
678
679 if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_PORT &&
680 cfgp->Header.PageNumber == 0) {
681 amt = sizeof (fCONFIG_PAGE_SCSI_PORT_0);
682 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_PORT &&
683 cfgp->Header.PageNumber == 1) {
684 amt = sizeof (fCONFIG_PAGE_SCSI_PORT_1);
685 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_PORT &&
686 cfgp->Header.PageNumber == 2) {
687 amt = sizeof (fCONFIG_PAGE_SCSI_PORT_2);
688 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_DEVICE &&
689 cfgp->Header.PageNumber == 0) {
690 amt = sizeof (fCONFIG_PAGE_SCSI_DEVICE_0);
691 } else if (cfgp->Header.PageType == MPI_CONFIG_PAGETYPE_SCSI_DEVICE &&
692 cfgp->Header.PageNumber == 1) {
693 amt = sizeof (fCONFIG_PAGE_SCSI_DEVICE_1);
694 }
695 bcopy(hdr, ((caddr_t)req->req_vbuf)+CFG_DATA_OFF, amt);
696 /* Restore stripped out attributes */
697 hdr->PageType |= hdr_attr;
698
699 mpt_check_doorbell(mpt);
700 mpt_send_cmd(mpt, req);
701 count = 0;
702 do {
703 DELAY(500);
704 mpt_intr(mpt);
705 if (++count == 1000) {
706 hdr->PageType |= hdr_attr;
707 mpt_prt(mpt, "mpt_write_cfg_page timed out");
708 return (-1);
709 }
710 } while (req->debug == REQ_ON_CHIP);
711
712 reply = (MSG_CONFIG_REPLY *) MPT_REPLY_PTOV(mpt, req->sequence);
713 if ((reply->IOCStatus & MPI_IOCSTATUS_MASK) != MPI_IOCSTATUS_SUCCESS) {
714 mpt_prt(mpt, "mpt_write_cfg_page: Config Info Status %x",
715 reply->IOCStatus);
716 mpt_free_reply(mpt, (req->sequence << 1));
717 return (-1);
718 }
719 mpt_free_reply(mpt, (req->sequence << 1));
720
721 mpt_free_request(mpt, req);
722 return (0);
723 }
724
725 /*
726 * Read SCSI configuration information
727 */
728 static int
729 mpt_read_config_info_spi(mpt_softc_t *mpt)
730 {
731 int rv, i;
732
733 rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_SCSI_PORT, 0,
734 0, &mpt->mpt_port_page0.Header);
735 if (rv) {
736 return (-1);
737 }
738 if (mpt->verbose > 1) {
739 mpt_prt(mpt, "SPI Port Page 0 Header: %x %x %x %x",
740 mpt->mpt_port_page0.Header.PageVersion,
741 mpt->mpt_port_page0.Header.PageLength,
742 mpt->mpt_port_page0.Header.PageNumber,
743 mpt->mpt_port_page0.Header.PageType);
744 }
745
746 rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_SCSI_PORT, 1,
747 0, &mpt->mpt_port_page1.Header);
748 if (rv) {
749 return (-1);
750 }
751 if (mpt->verbose > 1) {
752 mpt_prt(mpt, "SPI Port Page 1 Header: %x %x %x %x",
753 mpt->mpt_port_page1.Header.PageVersion,
754 mpt->mpt_port_page1.Header.PageLength,
755 mpt->mpt_port_page1.Header.PageNumber,
756 mpt->mpt_port_page1.Header.PageType);
757 }
758
759 rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_SCSI_PORT, 2,
760 0, &mpt->mpt_port_page2.Header);
761 if (rv) {
762 return (-1);
763 }
764
765 if (mpt->verbose > 1) {
766 mpt_prt(mpt, "SPI Port Page 2 Header: %x %x %x %x",
767 mpt->mpt_port_page1.Header.PageVersion,
768 mpt->mpt_port_page1.Header.PageLength,
769 mpt->mpt_port_page1.Header.PageNumber,
770 mpt->mpt_port_page1.Header.PageType);
771 }
772
773 for (i = 0; i < 16; i++) {
774 rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_SCSI_DEVICE,
775 0, i, &mpt->mpt_dev_page0[i].Header);
776 if (rv) {
777 return (-1);
778 }
779 if (mpt->verbose > 1) {
780 mpt_prt(mpt,
781 "SPI Target %d Device Page 0 Header: %x %x %x %x",
782 i, mpt->mpt_dev_page0[i].Header.PageVersion,
783 mpt->mpt_dev_page0[i].Header.PageLength,
784 mpt->mpt_dev_page0[i].Header.PageNumber,
785 mpt->mpt_dev_page0[i].Header.PageType);
786 }
787
788 rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_SCSI_DEVICE,
789 1, i, &mpt->mpt_dev_page1[i].Header);
790 if (rv) {
791 return (-1);
792 }
793 if (mpt->verbose > 1) {
794 mpt_prt(mpt,
795 "SPI Target %d Device Page 1 Header: %x %x %x %x",
796 i, mpt->mpt_dev_page1[i].Header.PageVersion,
797 mpt->mpt_dev_page1[i].Header.PageLength,
798 mpt->mpt_dev_page1[i].Header.PageNumber,
799 mpt->mpt_dev_page1[i].Header.PageType);
800 }
801 }
802
803 /*
804 * At this point, we don't *have* to fail. As long as we have
805 * valid config header information, we can (barely) lurch
806 * along.
807 */
808
809 rv = mpt_read_cfg_page(mpt, 0, &mpt->mpt_port_page0.Header);
810 if (rv) {
811 mpt_prt(mpt, "failed to read SPI Port Page 0");
812 } else if (mpt->verbose > 1) {
813 mpt_prt(mpt,
814 "SPI Port Page 0: Capabilities %x PhysicalInterface %x",
815 mpt->mpt_port_page0.Capabilities,
816 mpt->mpt_port_page0.PhysicalInterface);
817 }
818
819 rv = mpt_read_cfg_page(mpt, 0, &mpt->mpt_port_page1.Header);
820 if (rv) {
821 mpt_prt(mpt, "failed to read SPI Port Page 1");
822 } else if (mpt->verbose > 1) {
823 mpt_prt(mpt,
824 "SPI Port Page 1: Configuration %x OnBusTimerValue %x",
825 mpt->mpt_port_page1.Configuration,
826 mpt->mpt_port_page1.OnBusTimerValue);
827 }
828
829 rv = mpt_read_cfg_page(mpt, 0, &mpt->mpt_port_page2.Header);
830 if (rv) {
831 mpt_prt(mpt, "failed to read SPI Port Page 2");
832 } else if (mpt->verbose > 1) {
833 mpt_prt(mpt,
834 "SPI Port Page 2: Flags %x Settings %x",
835 mpt->mpt_port_page2.PortFlags,
836 mpt->mpt_port_page2.PortSettings);
837 for (i = 0; i < 16; i++) {
838 mpt_prt(mpt,
839 "SPI Port Page 2 Tgt %d: timo %x SF %x Flags %x",
840 i, mpt->mpt_port_page2.DeviceSettings[i].Timeout,
841 mpt->mpt_port_page2.DeviceSettings[i].SyncFactor,
842 mpt->mpt_port_page2.DeviceSettings[i].DeviceFlags);
843 }
844 }
845
846 for (i = 0; i < 16; i++) {
847 rv = mpt_read_cfg_page(mpt, i, &mpt->mpt_dev_page0[i].Header);
848 if (rv) {
849 mpt_prt(mpt, "cannot read SPI Tgt %d Device Page 0", i);
850 continue;
851 }
852 if (mpt->verbose > 1) {
853 mpt_prt(mpt,
854 "SPI Tgt %d Page 0: NParms %x Information %x",
855 i, mpt->mpt_dev_page0[i].NegotiatedParameters,
856 mpt->mpt_dev_page0[i].Information);
857 }
858 rv = mpt_read_cfg_page(mpt, i, &mpt->mpt_dev_page1[i].Header);
859 if (rv) {
860 mpt_prt(mpt, "cannot read SPI Tgt %d Device Page 1", i);
861 continue;
862 }
863 if (mpt->verbose > 1) {
864 mpt_prt(mpt,
865 "SPI Tgt %d Page 1: RParms %x Configuration %x",
866 i, mpt->mpt_dev_page1[i].RequestedParameters,
867 mpt->mpt_dev_page1[i].Configuration);
868 }
869 }
870 return (0);
871 }
872
873 /*
874 * Validate SPI configuration information.
875 *
876 * In particular, validate SPI Port Page 1.
877 */
878 static int
879 mpt_set_initial_config_spi(mpt_softc_t *mpt)
880 {
881 int i, pp1val = ((1 << mpt->mpt_ini_id) << 16) | mpt->mpt_ini_id;
882
883 mpt->mpt_disc_enable = 0xff;
884 mpt->mpt_tag_enable = 0;
885
886 if (mpt->mpt_port_page1.Configuration != pp1val) {
887 fCONFIG_PAGE_SCSI_PORT_1 tmp;
888 mpt_prt(mpt,
889 "SPI Port Page 1 Config value bad (%x)- should be %x",
890 mpt->mpt_port_page1.Configuration, pp1val);
891 tmp = mpt->mpt_port_page1;
892 tmp.Configuration = pp1val;
893 if (mpt_write_cfg_page(mpt, 0, &tmp.Header)) {
894 return (-1);
895 }
896 if (mpt_read_cfg_page(mpt, 0, &tmp.Header)) {
897 return (-1);
898 }
899 if (tmp.Configuration != pp1val) {
900 mpt_prt(mpt,
901 "failed to reset SPI Port Page 1 Config value");
902 return (-1);
903 }
904 mpt->mpt_port_page1 = tmp;
905 }
906
907 for (i = 0; i < 16; i++) {
908 fCONFIG_PAGE_SCSI_DEVICE_1 tmp;
909 tmp = mpt->mpt_dev_page1[i];
910 tmp.RequestedParameters = 0;
911 tmp.Configuration = 0;
912 if (mpt->verbose > 1) {
913 mpt_prt(mpt,
914 "Set Tgt %d SPI DevicePage 1 values to %x 0 %x",
915 i, tmp.RequestedParameters, tmp.Configuration);
916 }
917 if (mpt_write_cfg_page(mpt, i, &tmp.Header)) {
918 return (-1);
919 }
920 if (mpt_read_cfg_page(mpt, i, &tmp.Header)) {
921 return (-1);
922 }
923 mpt->mpt_dev_page1[i] = tmp;
924 if (mpt->verbose > 1) {
925 mpt_prt(mpt,
926 "SPI Tgt %d Page 1: RParm %x Configuration %x", i,
927 mpt->mpt_dev_page1[i].RequestedParameters,
928 mpt->mpt_dev_page1[i].Configuration);
929 }
930 }
931 return (0);
932 }
933
934 /*
935 * Enable IOC port
936 */
937 static int
938 mpt_send_port_enable(mpt_softc_t *mpt, int port)
939 {
940 int count;
941 request_t *req;
942 MSG_PORT_ENABLE *enable_req;
943
944 req = mpt_get_request(mpt);
945
946 enable_req = req->req_vbuf;
947 bzero(enable_req, sizeof *enable_req);
948
949 enable_req->Function = MPI_FUNCTION_PORT_ENABLE;
950 enable_req->MsgContext = req->index | 0x80000000;
951 enable_req->PortNumber = port;
952
953 mpt_check_doorbell(mpt);
954 if (mpt->verbose > 1) {
955 mpt_prt(mpt, "enabling port %d", port);
956 }
957 mpt_send_cmd(mpt, req);
958
959 count = 0;
960 do {
961 DELAY(500);
962 mpt_intr(mpt);
963 if (++count == 100000) {
964 mpt_prt(mpt, "port enable timed out");
965 return (-1);
966 }
967 } while (req->debug == REQ_ON_CHIP);
968 mpt_free_request(mpt, req);
969 return (0);
970 }
971
972 /*
973 * Enable/Disable asynchronous event reporting.
974 *
975 * NB: this is the first command we send via shared memory
976 * instead of the handshake register.
977 */
978 static int
979 mpt_send_event_request(mpt_softc_t *mpt, int onoff)
980 {
981 request_t *req;
982 MSG_EVENT_NOTIFY *enable_req;
983
984 req = mpt_get_request(mpt);
985
986 enable_req = req->req_vbuf;
987 bzero(enable_req, sizeof *enable_req);
988
989 enable_req->Function = MPI_FUNCTION_EVENT_NOTIFICATION;
990 enable_req->MsgContext = req->index | 0x80000000;
991 enable_req->Switch = onoff;
992
993 mpt_check_doorbell(mpt);
994 if (mpt->verbose > 1) {
995 mpt_prt(mpt, "%sabling async events", onoff? "en" : "dis");
996 }
997 mpt_send_cmd(mpt, req);
998
999 return (0);
1000 }
1001
1002 /*
1003 * Un-mask the interrupts on the chip.
1004 */
1005 void
1006 mpt_enable_ints(mpt_softc_t *mpt)
1007 {
1008 /* Unmask every thing except door bell int */
1009 mpt_write(mpt, MPT_OFFSET_INTR_MASK, MPT_INTR_DB_MASK);
1010 }
1011
1012 /*
1013 * Mask the interrupts on the chip.
1014 */
1015 void
1016 mpt_disable_ints(mpt_softc_t *mpt)
1017 {
1018 /* Mask all interrupts */
1019 mpt_write(mpt, MPT_OFFSET_INTR_MASK,
1020 MPT_INTR_REPLY_MASK | MPT_INTR_DB_MASK);
1021 }
1022
1023 /* (Re)Initialize the chip for use */
1024 int
1025 mpt_init(mpt_softc_t *mpt, u_int32_t who)
1026 {
1027 int try;
1028 MSG_IOC_FACTS_REPLY facts;
1029 MSG_PORT_FACTS_REPLY pfp;
1030 u_int32_t pptr;
1031 int val;
1032
1033 /* Put all request buffers (back) on the free list */
1034 SLIST_INIT(&mpt->request_free_list);
1035 for (val = 0; val < MPT_MAX_REQUESTS(mpt); val++) {
1036 mpt_free_request(mpt, &mpt->request_pool[val]);
1037 }
1038
1039 if (mpt->verbose > 1) {
1040 mpt_prt(mpt, "doorbell req = %s",
1041 mpt_ioc_diag(mpt_read(mpt, MPT_OFFSET_DOORBELL)));
1042 }
1043
1044 /*
1045 * Start by making sure we're not at FAULT or RESET state
1046 */
1047 switch (mpt_rd_db(mpt) & MPT_DB_STATE_MASK) {
1048 case MPT_DB_STATE_RESET:
1049 case MPT_DB_STATE_FAULT:
1050 if (mpt_reset(mpt) != MPT_OK) {
1051 return (EIO);
1052 }
1053 default:
1054 break;
1055 }
1056
1057 for (try = 0; try < MPT_MAX_TRYS; try++) {
1058 /*
1059 * No need to reset if the IOC is already in the READY state.
1060 *
1061 * Force reset if initialization failed previously.
1062 * Note that a hard_reset of the second channel of a '929
1063 * will stop operation of the first channel. Hopefully, if the
1064 * first channel is ok, the second will not require a hard
1065 * reset.
1066 */
1067 if ((mpt_rd_db(mpt) & MPT_DB_STATE_MASK) !=
1068 MPT_DB_STATE_READY) {
1069 if (mpt_reset(mpt) != MPT_OK) {
1070 DELAY(10000);
1071 continue;
1072 }
1073 }
1074
1075 if (mpt_get_iocfacts(mpt, &facts) != MPT_OK) {
1076 mpt_prt(mpt, "mpt_get_iocfacts failed");
1077 continue;
1078 }
1079
1080 if (mpt->verbose > 1) {
1081 mpt_prt(mpt,
1082 "IOCFACTS: GlobalCredits=%d BlockSize=%u "
1083 "Request Frame Size %u\n", facts.GlobalCredits,
1084 facts.BlockSize, facts.RequestFrameSize);
1085 }
1086 mpt->mpt_global_credits = facts.GlobalCredits;
1087 mpt->request_frame_size = facts.RequestFrameSize;
1088
1089 if (mpt_get_portfacts(mpt, &pfp) != MPT_OK) {
1090 mpt_prt(mpt, "mpt_get_portfacts failed");
1091 continue;
1092 }
1093
1094 if (mpt->verbose > 1) {
1095 mpt_prt(mpt,
1096 "PORTFACTS: Type %x PFlags %x IID %d MaxDev %d\n",
1097 pfp.PortType, pfp.ProtocolFlags, pfp.PortSCSIID,
1098 pfp.MaxDevices);
1099 }
1100
1101 if (pfp.PortType != MPI_PORTFACTS_PORTTYPE_SCSI &&
1102 pfp.PortType != MPI_PORTFACTS_PORTTYPE_FC) {
1103 mpt_prt(mpt, "Unsupported Port Type (%x)",
1104 pfp.PortType);
1105 return (ENXIO);
1106 }
1107 if (!(pfp.ProtocolFlags & MPI_PORTFACTS_PROTOCOL_INITIATOR)) {
1108 mpt_prt(mpt, "initiator role unsupported");
1109 return (ENXIO);
1110 }
1111 if (pfp.PortType == MPI_PORTFACTS_PORTTYPE_FC) {
1112 mpt->is_fc = 1;
1113 } else {
1114 mpt->is_fc = 0;
1115 }
1116 mpt->mpt_ini_id = pfp.PortSCSIID;
1117
1118 if (mpt_send_ioc_init(mpt, who) != MPT_OK) {
1119 mpt_prt(mpt, "mpt_send_ioc_init failed");
1120 continue;
1121 }
1122
1123 if (mpt->verbose > 1) {
1124 mpt_prt(mpt, "mpt_send_ioc_init ok");
1125 }
1126
1127 if (mpt_wait_state(mpt, MPT_DB_STATE_RUNNING) != MPT_OK) {
1128 mpt_prt(mpt, "IOC failed to go to run state");
1129 continue;
1130 }
1131 if (mpt->verbose > 1) {
1132 mpt_prt(mpt, "IOC now at RUNSTATE");
1133 }
1134
1135 /*
1136 * Give it reply buffers
1137 *
1138 * Do *not* except global credits.
1139 */
1140 for (val = 0, pptr = mpt->reply_phys;
1141 (pptr + MPT_REPLY_SIZE) < (mpt->reply_phys + PAGE_SIZE);
1142 pptr += MPT_REPLY_SIZE) {
1143 mpt_free_reply(mpt, pptr);
1144 if (++val == mpt->mpt_global_credits - 1)
1145 break;
1146 }
1147
1148 /*
1149 * Enable asynchronous event reporting
1150 */
1151 mpt_send_event_request(mpt, 1);
1152
1153
1154 /*
1155 * Read set up initial configuration information
1156 * (SPI only for now)
1157 */
1158
1159 if (mpt->is_fc == 0) {
1160 if (mpt_read_config_info_spi(mpt)) {
1161 return (EIO);
1162 }
1163 if (mpt_set_initial_config_spi(mpt)) {
1164 return (EIO);
1165 }
1166 }
1167
1168 /*
1169 * Now enable the port
1170 */
1171 if (mpt_send_port_enable(mpt, 0) != MPT_OK) {
1172 mpt_prt(mpt, "failed to enable port 0");
1173 continue;
1174 }
1175
1176 if (mpt->verbose > 1) {
1177 mpt_prt(mpt, "enabled port 0");
1178 }
1179
1180 /* Everything worked */
1181 break;
1182 }
1183
1184 if (try >= MPT_MAX_TRYS) {
1185 mpt_prt(mpt, "failed to initialize IOC");
1186 return (EIO);
1187 }
1188
1189 if (mpt->verbose > 1) {
1190 mpt_prt(mpt, "enabling interrupts");
1191 }
1192
1193 mpt_enable_ints(mpt);
1194 return (0);
1195 }
Cache object: 67335b599f4b04f74508dd3414684146
|