summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/lsilogic.c
blob: 9137fd8cf6f8554f095265f3f6628feb73edf5ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
/* $Id: lsilogic.c $ */
/** @file
 * LsiLogic SCSI host adapter driver to boot from disks.
 */

/*
 * Copyright (C) 2021-2022 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */

#include <stdint.h>
#include <string.h>
#include "biosint.h"
#include "ebda.h"
#include "inlines.h"
#include "pciutil.h"
#include "vds.h"
#include "scsi.h"

//#define DEBUG_LSILOGIC 1
#if DEBUG_LSILOGIC
# define DBG_LSILOGIC(...)        BX_INFO(__VA_ARGS__)
#else
# define DBG_LSILOGIC(...)
#endif

#define RT_BIT(bit) (1L << (bit))

/**
 * A simple SG element for a 32bit address.
 */
typedef struct MptSGEntrySimple32
{
    /** Length of the buffer this entry describes. */
    uint32_t u24Length:          24;
    /** Flag whether this element is the end of the list. */
    uint32_t fEndOfList:          1;
    /** Flag whether the address is 32bit or 64bits wide. */
    uint32_t f64BitAddress:       1;
    /** Flag whether this buffer contains data to be transferred or is the destination. */
    uint32_t fBufferContainsData: 1;
    /** Flag whether this is a local address or a system address. */
    uint32_t fLocalAddress:       1;
    /** Element type. */
    uint32_t u2ElementType:       2;
    /** Flag whether this is the last element of the buffer. */
    uint32_t fEndOfBuffer:        1;
    /** Flag whether this is the last element of the current segment. */
    uint32_t fLastElement:        1;
    /** Lower 32bits of the address of the data buffer. */
    uint32_t u32DataBufferAddressLow: 32;
} MptSGEntrySimple32, *PMptSGEntrySimple32;

/** Defined function codes found in the message header. */
#define MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST        (0x00)
#define MPT_MESSAGE_HDR_FUNCTION_IOC_INIT               (0x02)

/**
 * SCSI IO Request
 */
typedef struct MptSCSIIORequest
{
    /** Target ID */
    uint8_t     u8TargetID;
    /** Bus number */
    uint8_t     u8Bus;
    /** Chain offset */
    uint8_t     u8ChainOffset;
    /** Function number. */
    uint8_t     u8Function;
    /** CDB length. */
    uint8_t     u8CDBLength;
    /** Sense buffer length. */
    uint8_t     u8SenseBufferLength;
    /** Reserved */
    uint8_t     u8Reserved;
    /** Message flags. */
    uint8_t     u8MessageFlags;
    /** Message context ID. */
    uint32_t    u32MessageContext;
    /** LUN */
    uint8_t     au8LUN[8];
    /** Control values. */
    uint32_t    u32Control;
    /** The CDB. */
    uint8_t     au8CDB[16];
    /** Data length. */
    uint32_t    u32DataLength;
    /** Sense buffer low 32bit address. */
    uint32_t    u32SenseBufferLowAddress;
} MptSCSIIORequest, *PMptSCSIIORequest;

#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_NONE  (0x0L)
#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE (0x1L)
#define MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ  (0x2L)

/**
 * SCSI IO error reply.
 */
typedef struct MptSCSIIOErrorReply
{
    /** Target ID */
    uint8_t     u8TargetID;
    /** Bus number */
    uint8_t     u8Bus;
    /** Message length. */
    uint8_t     u8MessageLength;
    /** Function number. */
    uint8_t     u8Function;
    /** CDB length */
    uint8_t     u8CDBLength;
    /** Sense buffer length */
    uint8_t     u8SenseBufferLength;
    /** Reserved */
    uint8_t     u8Reserved;
    /** Message flags */
    uint8_t     u8MessageFlags;
    /** Message context ID */
    uint32_t    u32MessageContext;
    /** SCSI status. */
    uint8_t     u8SCSIStatus;
    /** SCSI state */
    uint8_t     u8SCSIState;
    /** IO controller status */
    uint16_t    u16IOCStatus;
    /** IO controller log information */
    uint32_t    u32IOCLogInfo;
    /** Transfer count */
    uint32_t    u32TransferCount;
    /** Sense count */
    uint32_t    u32SenseCount;
    /** Response information */
    uint32_t    u32ResponseInfo;
} MptSCSIIOErrorReply, *PMptSCSIIOErrorReply;

/**
 * IO controller init request.
 */
typedef struct MptIOCInitRequest
{
    /** Which system send this init request. */
    uint8_t     u8WhoInit;
    /** Reserved */
    uint8_t     u8Reserved;
    /** Chain offset in the SG list. */
    uint8_t     u8ChainOffset;
    /** Function to execute. */
    uint8_t     u8Function;
    /** Flags */
    uint8_t     u8Flags;
    /** Maximum number of devices the driver can handle. */
    uint8_t     u8MaxDevices;
    /** Maximum number of buses the driver can handle. */
    uint8_t     u8MaxBuses;
    /** Message flags. */
    uint8_t     u8MessageFlags;
    /** Message context ID. */
    uint32_t    u32MessageContext;
    /** Reply frame size. */
    uint16_t    u16ReplyFrameSize;
    /** Reserved */
    uint16_t    u16Reserved;
    /** Upper 32bit part of the 64bit address the message frames are in.
     *  That means all frames must be in the same 4GB segment. */
    uint32_t    u32HostMfaHighAddr;
    /** Upper 32bit of the sense buffer. */
    uint32_t    u32SenseBufferHighAddr;
} MptIOCInitRequest, *PMptIOCInitRequest;

#define LSILOGICWHOINIT_SYSTEM_BIOS 0x01


/**
 * IO controller init reply.
 */
typedef struct MptIOCInitReply
{
    /** Which subsystem send this init request. */
    uint8_t     u8WhoInit;
    /** Reserved */
    uint8_t     u8Reserved;
    /** Message length */
    uint8_t     u8MessageLength;
    /** Function. */
    uint8_t     u8Function;
    /** Flags */
    uint8_t     u8Flags;
    /** Maximum number of devices the driver can handle. */
    uint8_t     u8MaxDevices;
    /** Maximum number of busses the driver can handle. */
    uint8_t     u8MaxBuses;
    /** Message flags. */
    uint8_t     u8MessageFlags;
    /** Message context ID */
    uint32_t    u32MessageContext;
    /** Reserved */
    uint16_t    u16Reserved;
    /** IO controller status. */
    uint16_t    u16IOCStatus;
    /** IO controller log information. */
    uint32_t    u32IOCLogInfo;
} MptIOCInitReply, *PMptIOCInitReply;

/**
 * Doorbell register - Used to get the status of the controller and
 * initialise it.
 */
#define LSILOGIC_REG_DOORBELL 0x00
# define LSILOGIC_REG_DOORBELL_SET_STATE(enmState)     (((enmState) & 0x0f) << 28)
# define LSILOGIC_REG_DOORBELL_SET_USED(enmDoorbell)   (((enmDoorbell != LSILOGICDOORBELLSTATE_NOT_IN_USE) ? 1 : 0) << 27)
# define LSILOGIC_REG_DOORBELL_SET_WHOINIT(enmWhoInit) (((enmWhoInit) & 0x07) << 24)
# define LSILOGIC_REG_DOORBELL_SET_FAULT_CODE(u16Code) (u16Code)
# define LSILOGIC_REG_DOORBELL_GET_FUNCTION(x)         (((x) & 0xff000000) >> 24)
# define LSILOGIC_REG_DOORBELL_GET_SIZE(x)             (((x) & 0x00ff0000) >> 16)

/**
 * Functions which can be passed through the system doorbell.
 */
#define LSILOGIC_DOORBELL_FUNCTION_IOC_MSG_UNIT_RESET  0x40L
#define LSILOGIC_DOORBELL_FUNCTION_IO_UNIT_RESET       0x41L
#define LSILOGIC_DOORBELL_FUNCTION_HANDSHAKE           0x42L
#define LSILOGIC_DOORBELL_FUNCTION_REPLY_FRAME_REMOVAL 0x43L

/**
 * Write sequence register for the diagnostic register.
 */
#define LSILOGIC_REG_WRITE_SEQUENCE    0x04

/**
 * Diagnostic register - used to reset the controller.
 */
#define LSILOGIC_REG_HOST_DIAGNOSTIC   0x08
# define LSILOGIC_REG_HOST_DIAGNOSTIC_DIAG_MEM_ENABLE     (RT_BIT(0))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_DISABLE_ARM         (RT_BIT(1))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_RESET_ADAPTER       (RT_BIT(2))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_DIAG_RW_ENABLE      (RT_BIT(4))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_RESET_HISTORY       (RT_BIT(5))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_FLASH_BAD_SIG       (RT_BIT(6))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_DRWE                (RT_BIT(7))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_PREVENT_IOC_BOOT    (RT_BIT(9))
# define LSILOGIC_REG_HOST_DIAGNOSTIC_CLEAR_FLASH_BAD_SIG (RT_BIT(10))

#define LSILOGIC_REG_TEST_BASE_ADDRESS 0x0c
#define LSILOGIC_REG_DIAG_RW_DATA      0x10
#define LSILOGIC_REG_DIAG_RW_ADDRESS   0x14

/**
 * Interrupt status register.
 */
#define LSILOGIC_REG_HOST_INTR_STATUS  0x30
# define LSILOGIC_REG_HOST_INTR_STATUS_W_MASK (RT_BIT(3))
# define LSILOGIC_REG_HOST_INTR_STATUS_DOORBELL_STS    (RT_BIT(31))
# define LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR      (RT_BIT(3))
# define LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL (RT_BIT(0))

/**
 * Interrupt mask register.
 */
#define LSILOGIC_REG_HOST_INTR_MASK    0x34
# define LSILOGIC_REG_HOST_INTR_MASK_W_MASK (RT_BIT(0) | RT_BIT(3) | RT_BIT(8) | RT_BIT(9))
# define LSILOGIC_REG_HOST_INTR_MASK_IRQ_ROUTING (RT_BIT(8) | RT_BIT(9))
# define LSILOGIC_REG_HOST_INTR_MASK_DOORBELL RT_BIT(0)
# define LSILOGIC_REG_HOST_INTR_MASK_REPLY    RT_BIT(3)

/**
 * Queue registers.
 */
#define LSILOGIC_REG_REQUEST_QUEUE     0x40
#define LSILOGIC_REG_REPLY_QUEUE       0x44

/**
 * LsiLogic-SCSI controller data.
 */
typedef struct
{
    /** The SCSI I/O request structure. */
    MptSCSIIORequest   ScsiIoReq;
    /** S/G elements being used, must come after the I/O request structure. */
    MptSGEntrySimple32 Sge;
    /** The reply frame used for address replies. */
    uint8_t            abReply[128];
    /** I/O base of device. */
    uint16_t           u16IoBase;
} lsilogic_t;

/* The BusLogic specific data must fit into 1KB (statically allocated). */
ct_assert(sizeof(lsilogic_t) <= 1024);

#define VBOX_LSILOGIC_NO_DEVICE 0xffff

/* Warning: Destroys high bits of EAX. */
uint32_t inpd(uint16_t port);
#pragma aux inpd =      \
    ".386"              \
    "in     eax, dx"    \
    "mov    dx, ax"     \
    "shr    eax, 16"    \
    "xchg   ax, dx"     \
    parm [dx] value [dx ax] modify nomemory;

/* Warning: Destroys high bits of EAX. */
void outpd(uint16_t port, uint32_t val);
#pragma aux outpd =     \
    ".386"              \
    "xchg   ax, cx"     \
    "shl    eax, 16"    \
    "mov    ax, cx"     \
    "out    dx, eax"    \
    parm [dx] [cx ax] modify nomemory;

/**
 * Converts a segment:offset pair into a 32bit physical address.
 */
static uint32_t lsilogic_addr_to_phys(void __far *ptr)
{
    return ((uint32_t)FP_SEG(ptr) << 4) + FP_OFF(ptr);
}

static int lsilogic_cmd(lsilogic_t __far *lsilogic, const void __far *pvReq, uint16_t cbReq,
                        void __far *pvReply, uint16_t cbReply)
{
    uint16_t i;
    const uint32_t __far *pu32Req = (const uint32_t __far *)pvReq;
    uint16_t __far *pu16Reply = (uint16_t *)pvReply;
    uint32_t cMsg = cbReq / sizeof(uint32_t);
    uint16_t cReply = cbReply / sizeof(uint16_t);
    uint32_t u32Fn = (LSILOGIC_DOORBELL_FUNCTION_HANDSHAKE << 24) | (cMsg << 16);

    if (   cbReq % sizeof(uint32_t)
        || cbReply % sizeof(uint16_t))
        return 1;

    outpd(lsilogic->u16IoBase + LSILOGIC_REG_DOORBELL, u32Fn);
    for (i = 0; i < cMsg; i++)
        outpd(lsilogic->u16IoBase + LSILOGIC_REG_DOORBELL, pu32Req[i]);

    for (i = 0; i < cReply; i++)
    {
        /* Wait for the system doorbell interrupt status to be set. */
        while (!(inpd(lsilogic->u16IoBase + LSILOGIC_REG_HOST_INTR_STATUS) & LSILOGIC_REG_HOST_INTR_STATUS_SYSTEM_DOORBELL));

        pu16Reply[i] = (uint16_t)inpd(lsilogic->u16IoBase + LSILOGIC_REG_DOORBELL); /* The lower 16bits contain the reply data. */
        outpd(lsilogic->u16IoBase + LSILOGIC_REG_HOST_INTR_STATUS, 1);
    }

    return 0;
}

static int lsilogic_scsi_cmd_exec(lsilogic_t __far *lsilogic)
{
    uint32_t u32Reply = 0;
    uint32_t u32ReplyDummy = 0;

    /* Send it off. */
    outpd(lsilogic->u16IoBase + LSILOGIC_REG_REQUEST_QUEUE, lsilogic_addr_to_phys(&lsilogic->ScsiIoReq));

    /* Wait for it to finish. */
    while (!(inpd(lsilogic->u16IoBase + LSILOGIC_REG_HOST_INTR_STATUS) & LSILOGIC_REG_HOST_INTR_STATUS_REPLY_INTR));

    outpd(lsilogic->u16IoBase + LSILOGIC_REG_HOST_INTR_STATUS, 1);

    /* Read the reply queue. */
    u32Reply = inpd(lsilogic->u16IoBase + LSILOGIC_REG_REPLY_QUEUE);
    u32ReplyDummy = inpd(lsilogic->u16IoBase + LSILOGIC_REG_REPLY_QUEUE);
    if (u32ReplyDummy != 0xffffffff)
        return 5;
    if (u32Reply & RT_BIT(31))
    {
        /*
         * This is an address reply indicating a failed transaction, so just return an error without
         * bothering to check the exact failure reason for now.
         *
         * Just provide the reply frame to the reply queue again.
         */
        outpd(lsilogic->u16IoBase + LSILOGIC_REG_REPLY_QUEUE, lsilogic_addr_to_phys(&lsilogic->abReply));
        return 4;
    }

    if (u32Reply != 0xcafe) /* Getting a different context ID should never ever happen. */
        return 3;

    return 0;
}

int lsilogic_scsi_cmd_data_out(void __far *pvHba, uint8_t idTgt, uint8_t __far *aCDB,
                               uint8_t cbCDB, uint8_t __far *buffer, uint32_t length)
{
    lsilogic_t __far *lsilogic = (lsilogic_t __far *)pvHba;
    int i;

    _fmemset(&lsilogic->ScsiIoReq, 0, sizeof(lsilogic->ScsiIoReq));

    lsilogic->ScsiIoReq.u8TargetID          = idTgt;
    lsilogic->ScsiIoReq.u8Bus               = 0;
    lsilogic->ScsiIoReq.u8ChainOffset       = 0;
    lsilogic->ScsiIoReq.u8Function          = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST;
    lsilogic->ScsiIoReq.u8CDBLength         = cbCDB;
    lsilogic->ScsiIoReq.u8SenseBufferLength = 0;
    lsilogic->ScsiIoReq.u32MessageContext   = 0xcafe;
    lsilogic->ScsiIoReq.u32Control          = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_WRITE << 24;
    lsilogic->ScsiIoReq.u32DataLength       = length;
    for (i = 0; i < cbCDB; i++)
        lsilogic->ScsiIoReq.au8CDB[i] = aCDB[i];

    lsilogic->Sge.u24Length               = length;
    lsilogic->Sge.fEndOfList              = 1;
    lsilogic->Sge.f64BitAddress           = 0;
    lsilogic->Sge.fBufferContainsData     = 0;
    lsilogic->Sge.fLocalAddress           = 0;
    lsilogic->Sge.u2ElementType           = 0x01; /* Simple type */
    lsilogic->Sge.fEndOfBuffer            = 1;
    lsilogic->Sge.fLastElement            = 1;
    lsilogic->Sge.u32DataBufferAddressLow = lsilogic_addr_to_phys(buffer);

    return lsilogic_scsi_cmd_exec(lsilogic);
}

int lsilogic_scsi_cmd_data_in(void __far *pvHba, uint8_t idTgt, uint8_t __far *aCDB,
                              uint8_t cbCDB, uint8_t __far *buffer, uint32_t length)
{
    lsilogic_t __far *lsilogic = (lsilogic_t __far *)pvHba;
    int i;

    _fmemset(&lsilogic->ScsiIoReq, 0, sizeof(lsilogic->ScsiIoReq));

    lsilogic->ScsiIoReq.u8TargetID          = idTgt;
    lsilogic->ScsiIoReq.u8Bus               = 0;
    lsilogic->ScsiIoReq.u8ChainOffset       = 0;
    lsilogic->ScsiIoReq.u8Function          = MPT_MESSAGE_HDR_FUNCTION_SCSI_IO_REQUEST;
    lsilogic->ScsiIoReq.u8CDBLength         = cbCDB;
    lsilogic->ScsiIoReq.u8SenseBufferLength = 0;
    lsilogic->ScsiIoReq.u32MessageContext   = 0xcafe;
    lsilogic->ScsiIoReq.u32Control          = MPT_SCSIIO_REQUEST_CONTROL_TXDIR_READ << 24;
    lsilogic->ScsiIoReq.u32DataLength       = length;
    for (i = 0; i < cbCDB; i++)
        lsilogic->ScsiIoReq.au8CDB[i] = aCDB[i];

    lsilogic->Sge.u24Length                 = length;
    lsilogic->Sge.fEndOfList                = 1;
    lsilogic->Sge.f64BitAddress             = 0;
    lsilogic->Sge.fBufferContainsData       = 0;
    lsilogic->Sge.fLocalAddress             = 0;
    lsilogic->Sge.u2ElementType             = 0x01; /* Simple type */
    lsilogic->Sge.fEndOfBuffer              = 1;
    lsilogic->Sge.fLastElement              = 1;
    lsilogic->Sge.u32DataBufferAddressLow   = lsilogic_addr_to_phys(buffer);

    return lsilogic_scsi_cmd_exec(lsilogic);
}

/**
 * Initializes the LsiLogic SCSI HBA and detects attached devices.
 */
static int lsilogic_scsi_hba_init(lsilogic_t __far *lsilogic)
{
    int                 rc;
    MptIOCInitRequest   IocInitReq;
    MptIOCInitReply     IocInitReply;

    /*
     * The following initialization sequence is stripped down to the point to work with
     * our emulated LsiLogic controller, it will most certainly fail on real hardware.
     */

    /* Hard reset, write the sequence to enable the diagnostic access. */
    outpd(lsilogic->u16IoBase + LSILOGIC_REG_WRITE_SEQUENCE, 0x04);
    outpd(lsilogic->u16IoBase + LSILOGIC_REG_WRITE_SEQUENCE, 0x02);
    outpd(lsilogic->u16IoBase + LSILOGIC_REG_WRITE_SEQUENCE, 0x07);
    outpd(lsilogic->u16IoBase + LSILOGIC_REG_WRITE_SEQUENCE, 0x0d);
    outpd(lsilogic->u16IoBase + LSILOGIC_REG_HOST_DIAGNOSTIC, LSILOGIC_REG_HOST_DIAGNOSTIC_RESET_ADAPTER);

    IocInitReq.u8WhoInit              = LSILOGICWHOINIT_SYSTEM_BIOS;
    IocInitReq.u8Function             = MPT_MESSAGE_HDR_FUNCTION_IOC_INIT;
    IocInitReq.u32HostMfaHighAddr     = 0;
    IocInitReq.u32SenseBufferHighAddr = 0;
    IocInitReq.u8MaxBuses             = 1;
    IocInitReq.u8MaxDevices           = 4;
    IocInitReq.u16ReplyFrameSize      = sizeof(lsilogic->abReply);
    rc = lsilogic_cmd(lsilogic, &IocInitReq, sizeof(IocInitReq), &IocInitReply, sizeof(IocInitReply));
    if (!rc)
    {
        /* Provide a single reply frame for SCSI I/O errors. */
        outpd(lsilogic->u16IoBase + LSILOGIC_REG_REPLY_QUEUE, lsilogic_addr_to_phys(&lsilogic->abReply));
        return 0;
    }

    return 1;
}

/**
 * Init the LsiLogic SCSI driver and detect attached disks.
 */
int lsilogic_scsi_init(void __far *pvHba, uint8_t u8Bus, uint8_t u8DevFn)
{
    lsilogic_t __far *lsilogic = (lsilogic_t __far *)pvHba;
    uint32_t u32Bar;

    DBG_LSILOGIC("LsiLogic SCSI HBA at Bus %u DevFn 0x%x (raw 0x%x)\n", u8Bus, u8DevFn);

    u32Bar = pci_read_config_dword(u8Bus, u8DevFn, 0x10);

    DBG_LSILOGIC("BAR at 0x10 : 0x%x\n", u32Bar);

    if ((u32Bar & 0x01) != 0)
    {
        uint16_t u16IoBase = (u32Bar & 0xfff0);

        /* Enable PCI memory, I/O, bus mastering access in command register. */
        pci_write_config_word(u8Bus, u8DevFn, 4, 0x7);

        DBG_LSILOGIC("I/O base: 0x%x\n", u16IoBase);
        lsilogic->u16IoBase = u16IoBase;
        return lsilogic_scsi_hba_init(lsilogic);
    }
    else
        DBG_LSILOGIC("BAR is MMIO\n");

    return 1;
}