SCSI mailbox processing is now threaded.
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
*
|
*
|
||||||
* NOTE: THIS IS CURRENTLY A MESS, but will be cleaned up as I go.
|
* NOTE: THIS IS CURRENTLY A MESS, but will be cleaned up as I go.
|
||||||
*
|
*
|
||||||
* Version: @(#)scsi_aha154x.c 1.0.14 2017/08/27
|
* Version: @(#)scsi_aha154x.c 1.0.15 2017/09/01
|
||||||
*
|
*
|
||||||
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
|
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
|
||||||
* Original Buslogic version by SA1988 and Miran Grca.
|
* Original Buslogic version by SA1988 and Miran Grca.
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "../pic.h"
|
#include "../pic.h"
|
||||||
#include "../timer.h"
|
#include "../timer.h"
|
||||||
#include "../device.h"
|
#include "../device.h"
|
||||||
|
#include "../win/plat_thread.h"
|
||||||
#include "scsi.h"
|
#include "scsi.h"
|
||||||
#include "scsi_bios_command.h"
|
#include "scsi_bios_command.h"
|
||||||
#include "scsi_device.h"
|
#include "scsi_device.h"
|
||||||
@@ -393,12 +394,8 @@ typedef struct {
|
|||||||
wchar_t *nvr_path; /* path to NVR image file */
|
wchar_t *nvr_path; /* path to NVR image file */
|
||||||
uint8_t *nvr; /* EEPROM buffer */
|
uint8_t *nvr; /* EEPROM buffer */
|
||||||
|
|
||||||
int Callback;
|
|
||||||
int InOperation;
|
|
||||||
int ResetCB;
|
int ResetCB;
|
||||||
|
|
||||||
int8_t IrqEnabled;
|
|
||||||
int StrictRoundRobinMode;
|
|
||||||
int ExtendedLUNCCBFormat;
|
int ExtendedLUNCCBFormat;
|
||||||
Req_t Req;
|
Req_t Req;
|
||||||
uint8_t Status;
|
uint8_t Status;
|
||||||
@@ -422,6 +419,8 @@ typedef struct {
|
|||||||
int MbiActive[256];
|
int MbiActive[256];
|
||||||
int PendingInterrupt;
|
int PendingInterrupt;
|
||||||
int Lock;
|
int Lock;
|
||||||
|
event_t *evt;
|
||||||
|
int scan_restart;
|
||||||
} aha_t;
|
} aha_t;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
@@ -431,6 +430,11 @@ static uint16_t aha_ports[] = {
|
|||||||
0x0130, 0x0134, 0x0000, 0x0000
|
0x0130, 0x0134, 0x0000, 0x0000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void aha_cmd_thread(void *priv);
|
||||||
|
static thread_t *poll_tid;
|
||||||
|
|
||||||
|
|
||||||
#if ENABLE_AHA154X_LOG
|
#if ENABLE_AHA154X_LOG
|
||||||
int aha_do_log = ENABLE_AHA154X_LOG;
|
int aha_do_log = ENABLE_AHA154X_LOG;
|
||||||
#endif
|
#endif
|
||||||
@@ -575,6 +579,42 @@ aha154x_mmap(aha_t *dev, uint8_t cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
RaiseIntr(aha_t *dev, int suppress, uint8_t Interrupt)
|
||||||
|
{
|
||||||
|
if (Interrupt & (INTR_MBIF | INTR_MBOA))
|
||||||
|
{
|
||||||
|
if (!(dev->Interrupt & INTR_HACC))
|
||||||
|
{
|
||||||
|
dev->Interrupt |= Interrupt; /* Report now. */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dev->PendingInterrupt |= Interrupt; /* Report later. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Interrupt & INTR_HACC)
|
||||||
|
{
|
||||||
|
if (dev->Interrupt == 0 || dev->Interrupt == (INTR_ANY | INTR_HACC))
|
||||||
|
{
|
||||||
|
aha_log("%s: BuslogicRaiseInterrupt(): Interrupt=%02X\n", dev->name, dev->Interrupt);
|
||||||
|
}
|
||||||
|
dev->Interrupt |= Interrupt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
aha_log("%s: BuslogicRaiseInterrupt(): Invalid interrupt state!\n", dev->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->Interrupt |= INTR_ANY;
|
||||||
|
|
||||||
|
if (!suppress)
|
||||||
|
{
|
||||||
|
picint(1 << dev->Irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ClearIntr(aha_t *dev)
|
ClearIntr(aha_t *dev)
|
||||||
{
|
{
|
||||||
@@ -583,52 +623,41 @@ ClearIntr(aha_t *dev)
|
|||||||
dev->name, dev->Irq, dev->Interrupt);
|
dev->name, dev->Irq, dev->Interrupt);
|
||||||
picintc(1 << dev->Irq);
|
picintc(1 << dev->Irq);
|
||||||
if (dev->PendingInterrupt) {
|
if (dev->PendingInterrupt) {
|
||||||
dev->Interrupt = dev->PendingInterrupt;
|
|
||||||
aha_log("%s: Raising Interrupt 0x%02X (Pending)\n",
|
aha_log("%s: Raising Interrupt 0x%02X (Pending)\n",
|
||||||
dev->name, dev->Interrupt);
|
dev->name, dev->Interrupt);
|
||||||
if (dev->MailboxOutInterrupts || !(dev->Interrupt & INTR_MBOA)) {
|
if (dev->MailboxOutInterrupts || !(dev->Interrupt & INTR_MBOA)) {
|
||||||
if (dev->IrqEnabled) picint(1 << dev->Irq);
|
RaiseIntr(dev, 0, dev->PendingInterrupt);
|
||||||
}
|
}
|
||||||
dev->PendingInterrupt = 0;
|
dev->PendingInterrupt = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
RaiseIntr(aha_t *dev, uint8_t Interrupt)
|
|
||||||
{
|
|
||||||
if (dev->Interrupt & INTR_HACC) {
|
|
||||||
aha_log("%s: Pending IRQ\n", dev->name);
|
|
||||||
dev->PendingInterrupt = Interrupt;
|
|
||||||
} else {
|
|
||||||
dev->Interrupt = Interrupt;
|
|
||||||
aha_log("%s: Raising IRQ %i\n", dev->name, dev->Irq);
|
|
||||||
if (dev->IrqEnabled)
|
|
||||||
picint(1 << dev->Irq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
aha_reset(aha_t *dev)
|
aha_reset(aha_t *dev)
|
||||||
{
|
{
|
||||||
|
if (dev->evt) {
|
||||||
|
thread_destroy_event(dev->evt);
|
||||||
|
dev->evt = NULL;
|
||||||
|
if (poll_tid) {
|
||||||
|
poll_tid = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dev->ResetCB = 0;
|
dev->ResetCB = 0;
|
||||||
dev->Callback = 0;
|
dev->scan_restart = 0;
|
||||||
|
|
||||||
dev->Status = STAT_IDLE | STAT_INIT;
|
dev->Status = STAT_IDLE | STAT_INIT;
|
||||||
dev->Geometry = 0x80;
|
dev->Geometry = 0x80;
|
||||||
dev->Command = 0xFF;
|
dev->Command = 0xFF;
|
||||||
dev->CmdParam = 0;
|
dev->CmdParam = 0;
|
||||||
dev->CmdParamLeft = 0;
|
dev->CmdParamLeft = 0;
|
||||||
dev->IrqEnabled = 1;
|
|
||||||
dev->StrictRoundRobinMode = 0;
|
|
||||||
dev->ExtendedLUNCCBFormat = 0;
|
dev->ExtendedLUNCCBFormat = 0;
|
||||||
dev->MailboxOutPosCur = 0;
|
dev->MailboxOutPosCur = 0;
|
||||||
dev->MailboxInPosCur = 0;
|
dev->MailboxInPosCur = 0;
|
||||||
dev->MailboxOutInterrupts = 0;
|
dev->MailboxOutInterrupts = 0;
|
||||||
dev->PendingInterrupt = 0;
|
dev->PendingInterrupt = 0;
|
||||||
dev->Lock = 0;
|
dev->Lock = 0;
|
||||||
dev->InOperation = 0;
|
|
||||||
|
|
||||||
ClearIntr(dev);
|
ClearIntr(dev);
|
||||||
}
|
}
|
||||||
@@ -667,17 +696,15 @@ aha_reset_poll(void *priv)
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
aha_cmd_done(aha_t *dev)
|
aha_cmd_done(aha_t *dev, int suppress)
|
||||||
{
|
{
|
||||||
dev->DataReply = 0;
|
dev->DataReply = 0;
|
||||||
dev->Status |= STAT_IDLE;
|
dev->Status |= STAT_IDLE;
|
||||||
|
|
||||||
if ((dev->Command != CMD_START_SCSI) && (dev->Command != CMD_BIOS_SCSI)) {
|
if ((dev->Command != CMD_START_SCSI) && (dev->Command != CMD_BIOS_SCSI)) {
|
||||||
dev->Status &= ~STAT_DFULL;
|
dev->Status &= ~STAT_DFULL;
|
||||||
dev->Interrupt = (INTR_ANY | INTR_HACC);
|
|
||||||
aha_log("%s: Raising IRQ %i\n", dev->name, dev->Irq);
|
aha_log("%s: Raising IRQ %i\n", dev->name, dev->Irq);
|
||||||
if (dev->IrqEnabled)
|
RaiseIntr(dev, suppress, INTR_HACC);
|
||||||
picint(1 << dev->Irq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dev->Command = 0xff;
|
dev->Command = 0xff;
|
||||||
@@ -699,8 +726,6 @@ aha_mbi_setup(aha_t *dev, uint32_t CCBPointer, CCBU *CmdBlock,
|
|||||||
req->MailboxCompletionCode = mbcc;
|
req->MailboxCompletionCode = mbcc;
|
||||||
|
|
||||||
aha_log("Mailbox in setup\n");
|
aha_log("Mailbox in setup\n");
|
||||||
|
|
||||||
dev->InOperation = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -755,9 +780,7 @@ aha_mbi(aha_t *dev)
|
|||||||
if (dev->MailboxInPosCur >= dev->MailboxCount)
|
if (dev->MailboxInPosCur >= dev->MailboxCount)
|
||||||
dev->MailboxInPosCur = 0;
|
dev->MailboxInPosCur = 0;
|
||||||
|
|
||||||
RaiseIntr(dev, INTR_MBIF | INTR_ANY);
|
RaiseIntr(dev, 0, INTR_MBIF | INTR_ANY);
|
||||||
|
|
||||||
dev->InOperation = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1113,6 +1136,8 @@ aha_req_setup(aha_t *dev, uint32_t CCBPointer, Mailbox32_t *Mailbox32)
|
|||||||
if ((id > max_id) || (lun > 7)) {
|
if ((id > max_id) || (lun > 7)) {
|
||||||
aha_mbi_setup(dev, CCBPointer, &req->CmdBlock,
|
aha_mbi_setup(dev, CCBPointer, &req->CmdBlock,
|
||||||
CCB_INVALID_CCB, SCSI_STATUS_OK, MBI_ERROR);
|
CCB_INVALID_CCB, SCSI_STATUS_OK, MBI_ERROR);
|
||||||
|
aha_log("%s: Callback: Send incoming mailbox\n", dev->name);
|
||||||
|
aha_mbi(dev);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1129,6 +1154,8 @@ aha_req_setup(aha_t *dev, uint32_t CCBPointer, Mailbox32_t *Mailbox32)
|
|||||||
SenseBufferFree(req, 0);
|
SenseBufferFree(req, 0);
|
||||||
aha_mbi_setup(dev, CCBPointer, &req->CmdBlock,
|
aha_mbi_setup(dev, CCBPointer, &req->CmdBlock,
|
||||||
CCB_SELECTION_TIMEOUT,SCSI_STATUS_OK,MBI_ERROR);
|
CCB_SELECTION_TIMEOUT,SCSI_STATUS_OK,MBI_ERROR);
|
||||||
|
aha_log("%s: Callback: Send incoming mailbox\n", dev->name);
|
||||||
|
aha_mbi(dev);
|
||||||
} else {
|
} else {
|
||||||
aha_log("SCSI Target ID %i and LUN %i detected and working\n", id, lun);
|
aha_log("SCSI Target ID %i and LUN %i detected and working\n", id, lun);
|
||||||
|
|
||||||
@@ -1140,7 +1167,11 @@ aha_req_setup(aha_t *dev, uint32_t CCBPointer, Mailbox32_t *Mailbox32)
|
|||||||
req->CmdBlock.common.ControlByte);
|
req->CmdBlock.common.ControlByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
dev->InOperation = 1;
|
aha_log("%s: Callback: Process SCSI request\n", dev->name);
|
||||||
|
aha_scsi_cmd(dev);
|
||||||
|
|
||||||
|
aha_log("%s: Callback: Send incoming mailbox\n", dev->name);
|
||||||
|
aha_mbi(dev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1155,6 +1186,8 @@ aha_req_abort(aha_t *dev, uint32_t CCBPointer)
|
|||||||
|
|
||||||
aha_mbi_setup(dev, CCBPointer, &CmdBlock,
|
aha_mbi_setup(dev, CCBPointer, &CmdBlock,
|
||||||
0x26, SCSI_STATUS_OK, MBI_NOT_FOUND);
|
0x26, SCSI_STATUS_OK, MBI_NOT_FOUND);
|
||||||
|
aha_log("%s: Callback: Send incoming mailbox\n", dev->name);
|
||||||
|
aha_mbi(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1187,7 +1220,7 @@ aha_mbo_adv(aha_t *dev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static uint8_t
|
||||||
aha_do_mail(aha_t *dev)
|
aha_do_mail(aha_t *dev)
|
||||||
{
|
{
|
||||||
Mailbox32_t mb32;
|
Mailbox32_t mb32;
|
||||||
@@ -1197,32 +1230,25 @@ aha_do_mail(aha_t *dev)
|
|||||||
|
|
||||||
CodeOffset = dev->Mbx24bit ? 0 : 7;
|
CodeOffset = dev->Mbx24bit ? 0 : 7;
|
||||||
|
|
||||||
if (! dev->StrictRoundRobinMode) {
|
if (dev->Interrupt || dev->PendingInterrupt)
|
||||||
uint8_t MailboxCur = dev->MailboxOutPosCur;
|
{
|
||||||
|
aha_log("%s: Interrupt set, waiting...\n", dev->name);
|
||||||
/* Search for a filled mailbox - stop if we have scanned all mailboxes. */
|
return 1;
|
||||||
do {
|
|
||||||
/* Fetch mailbox from guest memory. */
|
|
||||||
Outgoing = aha_mbo(dev, &mb32);
|
|
||||||
|
|
||||||
/* Check the next mailbox. */
|
|
||||||
aha_mbo_adv(dev);
|
|
||||||
} while ((mb32.u.out.ActionCode == MBO_FREE) && (MailboxCur != dev->MailboxOutPosCur));
|
|
||||||
} else {
|
|
||||||
Outgoing = aha_mbo(dev, &mb32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Outgoing = aha_mbo(dev, &mb32);
|
||||||
|
|
||||||
if (mb32.u.out.ActionCode != MBO_FREE) {
|
if (mb32.u.out.ActionCode != MBO_FREE) {
|
||||||
/* We got the mailbox, mark it as free in the guest. */
|
/* We got the mailbox, mark it as free in the guest. */
|
||||||
aha_log("aha_do_mail(): Writing %i bytes at %08X\n", sizeof(CmdStatus), Outgoing + CodeOffset);
|
aha_log("aha_do_mail(): Writing %i bytes at %08X\n", sizeof(CmdStatus), Outgoing + CodeOffset);
|
||||||
DMAPageWrite(Outgoing + CodeOffset, (char *)&CmdStatus, sizeof(CmdStatus));
|
DMAPageWrite(Outgoing + CodeOffset, (char *)&CmdStatus, sizeof(CmdStatus));
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (dev->MailboxOutInterrupts)
|
if (dev->MailboxOutInterrupts)
|
||||||
RaiseIntr(dev, INTR_MBOA | INTR_ANY);
|
RaiseIntr(dev, 0, INTR_MBOA | INTR_ANY);
|
||||||
|
|
||||||
/* Check if the mailbox is actually loaded. */
|
|
||||||
if (mb32.u.out.ActionCode == MBO_FREE) return;
|
|
||||||
|
|
||||||
if (mb32.u.out.ActionCode == MBO_START) {
|
if (mb32.u.out.ActionCode == MBO_START) {
|
||||||
aha_log("Start Mailbox Command\n");
|
aha_log("Start Mailbox Command\n");
|
||||||
@@ -1235,40 +1261,42 @@ aha_do_mail(aha_t *dev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Advance to the next mailbox. */
|
/* Advance to the next mailbox. */
|
||||||
if (dev->StrictRoundRobinMode)
|
|
||||||
aha_mbo_adv(dev);
|
aha_mbo_adv(dev);
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
aha_cmd_cb(void *priv)
|
aha_cmd_thread(void *priv)
|
||||||
{
|
{
|
||||||
aha_t *dev = (aha_t *)priv;
|
aha_t *dev = (aha_t *)priv;
|
||||||
|
|
||||||
if (dev->InOperation == 0) {
|
aha_event_restart:
|
||||||
if (dev->MailboxCount) {
|
/* Create a waitable event. */
|
||||||
aha_do_mail(dev);
|
dev->evt = thread_create_event();
|
||||||
} else {
|
|
||||||
dev->Callback += SCSI_DELAY_TM * TIMER_USEC;
|
aha_scan_restart:
|
||||||
return;
|
while (aha_do_mail(dev) != 0)
|
||||||
}
|
{
|
||||||
} else if (dev->InOperation == 1) {
|
|
||||||
aha_log("%s: Callback: Process SCSI request\n", dev->name);
|
|
||||||
aha_scsi_cmd(dev);
|
|
||||||
aha_mbi(dev);
|
|
||||||
if (dev->Req.CmdBlock.common.Cdb[0] == 0x42) {
|
|
||||||
/* This is needed since CD Audio inevitably means READ SUBCHANNEL spam. */
|
|
||||||
dev->Callback += 1000 * TIMER_USEC;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (dev->InOperation == 2) {
|
|
||||||
aha_log("%s: Callback: Send incoming mailbox\n", dev->name);
|
|
||||||
aha_mbi(dev);
|
|
||||||
} else {
|
|
||||||
fatal("%s: Invalid callback phase: %i\n", dev->name, dev->InOperation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dev->Callback += SCSI_DELAY_TM * TIMER_USEC;
|
if (dev->scan_restart)
|
||||||
|
{
|
||||||
|
goto aha_scan_restart;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_destroy_event(dev->evt);
|
||||||
|
dev->evt = NULL;
|
||||||
|
|
||||||
|
if (dev->scan_restart)
|
||||||
|
{
|
||||||
|
goto aha_event_restart;
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_tid = NULL;
|
||||||
|
|
||||||
|
aha_log("%s: Callback: polling stopped.\n", dev->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1290,7 +1318,7 @@ aha_read(uint16_t port, void *priv)
|
|||||||
dev->DataReply++;
|
dev->DataReply++;
|
||||||
dev->DataReplyLeft--;
|
dev->DataReplyLeft--;
|
||||||
if (! dev->DataReplyLeft)
|
if (! dev->DataReplyLeft)
|
||||||
aha_cmd_done(dev);
|
aha_cmd_done(dev, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1329,6 +1357,7 @@ aha_write(uint16_t port, uint8_t val, void *priv)
|
|||||||
uint8_t j = 0;
|
uint8_t j = 0;
|
||||||
BIOSCMD *cmd;
|
BIOSCMD *cmd;
|
||||||
uint16_t cyl = 0;
|
uint16_t cyl = 0;
|
||||||
|
int suppress = 0;
|
||||||
|
|
||||||
aha_log("%s: Write Port 0x%02X, Value %02X\n", dev->name, port, val);
|
aha_log("%s: Write Port 0x%02X, Value %02X\n", dev->name, port, val);
|
||||||
|
|
||||||
@@ -1352,8 +1381,13 @@ aha_write(uint16_t port, uint8_t val, void *priv)
|
|||||||
(dev->Command == 0xff)) {
|
(dev->Command == 0xff)) {
|
||||||
/* If there are no mailboxes configured, don't even try to do anything. */
|
/* If there are no mailboxes configured, don't even try to do anything. */
|
||||||
if (dev->MailboxCount) {
|
if (dev->MailboxCount) {
|
||||||
if (! dev->Callback) {
|
if (! poll_tid) {
|
||||||
dev->Callback = SCSI_DELAY_TM * TIMER_USEC;
|
aha_log("%s: starting thread..\n", dev->name);
|
||||||
|
poll_tid = thread_create(aha_cmd_thread, dev);
|
||||||
|
dev->scan_restart = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dev->scan_restart = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1479,6 +1513,7 @@ aha_0x01:
|
|||||||
if (dev->CmdBuf[0] <= 1) {
|
if (dev->CmdBuf[0] <= 1) {
|
||||||
dev->MailboxOutInterrupts = dev->CmdBuf[0];
|
dev->MailboxOutInterrupts = dev->CmdBuf[0];
|
||||||
aha_log("Mailbox out interrupts: %s\n", dev->MailboxOutInterrupts ? "ON" : "OFF");
|
aha_log("Mailbox out interrupts: %s\n", dev->MailboxOutInterrupts ? "ON" : "OFF");
|
||||||
|
suppress = 1;
|
||||||
} else {
|
} else {
|
||||||
dev->Status |= STAT_INVCMD;
|
dev->Status |= STAT_INVCMD;
|
||||||
}
|
}
|
||||||
@@ -1648,7 +1683,7 @@ aha_0x01:
|
|||||||
if (dev->DataReplyLeft)
|
if (dev->DataReplyLeft)
|
||||||
dev->Status |= STAT_DFULL;
|
dev->Status |= STAT_DFULL;
|
||||||
else if (!dev->CmdParamLeft)
|
else if (!dev->CmdParamLeft)
|
||||||
aha_cmd_done(dev);
|
aha_cmd_done(dev, suppress);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
@@ -2062,7 +2097,6 @@ aha_init(int type)
|
|||||||
aha_setnvr(dev);
|
aha_setnvr(dev);
|
||||||
|
|
||||||
timer_add(aha_reset_poll, &dev->ResetCB, &dev->ResetCB, dev);
|
timer_add(aha_reset_poll, &dev->ResetCB, &dev->ResetCB, dev);
|
||||||
timer_add(aha_cmd_cb, &dev->Callback, &dev->Callback, dev);
|
|
||||||
|
|
||||||
if (dev->Base != 0) {
|
if (dev->Base != 0) {
|
||||||
/* Register our address space. */
|
/* Register our address space. */
|
||||||
@@ -2110,11 +2144,22 @@ aha_close(void *priv)
|
|||||||
{
|
{
|
||||||
aha_t *dev = (aha_t *)priv;
|
aha_t *dev = (aha_t *)priv;
|
||||||
|
|
||||||
|
if (dev)
|
||||||
|
{
|
||||||
|
if (dev->evt) {
|
||||||
|
thread_destroy_event(dev->evt);
|
||||||
|
dev->evt = NULL;
|
||||||
|
if (poll_tid) {
|
||||||
|
poll_tid = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dev->nvr != NULL)
|
if (dev->nvr != NULL)
|
||||||
free(dev->nvr);
|
free(dev->nvr);
|
||||||
|
|
||||||
free(dev);
|
free(dev);
|
||||||
|
dev = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
* 0 - BT-545C ISA;
|
* 0 - BT-545C ISA;
|
||||||
* 1 - BT-958D PCI (but BT-545C ISA on non-PCI machines)
|
* 1 - BT-958D PCI (but BT-545C ISA on non-PCI machines)
|
||||||
*
|
*
|
||||||
* Version: @(#)scsi_buslogic.c 1.0.10 2017/08/26
|
* Version: @(#)scsi_buslogic.c 1.0.11 2017/09/01
|
||||||
*
|
*
|
||||||
* Authors: TheCollector1995, <mariogplayer@gmail.com>
|
* Authors: TheCollector1995, <mariogplayer@gmail.com>
|
||||||
* Miran Grca, <mgrca8@gmail.com>
|
* Miran Grca, <mgrca8@gmail.com>
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "../pci.h"
|
#include "../pci.h"
|
||||||
#include "../timer.h"
|
#include "../timer.h"
|
||||||
#include "../device.h"
|
#include "../device.h"
|
||||||
|
#include "../win/plat_thread.h"
|
||||||
#include "scsi.h"
|
#include "scsi.h"
|
||||||
#include "scsi_bios_command.h"
|
#include "scsi_bios_command.h"
|
||||||
#include "scsi_device.h"
|
#include "scsi_device.h"
|
||||||
@@ -505,14 +506,17 @@ typedef struct {
|
|||||||
bios_mask;
|
bios_mask;
|
||||||
uint8_t AutoSCSIROM[32768];
|
uint8_t AutoSCSIROM[32768];
|
||||||
uint8_t SCAMData[65536];
|
uint8_t SCAMData[65536];
|
||||||
|
event_t *evt;
|
||||||
|
int scan_restart;
|
||||||
} Buslogic_t;
|
} Buslogic_t;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
|
||||||
static int BuslogicResetCallback = 0;
|
static int BuslogicResetCallback = 0;
|
||||||
static int BuslogicCallback = 0;
|
|
||||||
static int BuslogicInOperation = 0;
|
|
||||||
static Buslogic_t *BuslogicResetDevice;
|
static void BuslogicCommandThread(void *p);
|
||||||
|
static thread_t *poll_tid;
|
||||||
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -743,18 +747,6 @@ static void BuslogicInitializeAutoSCSIRam(Buslogic_t *bl)
|
|||||||
static void
|
static void
|
||||||
BuslogicRaiseInterrupt(Buslogic_t *bl, int suppress, uint8_t Interrupt)
|
BuslogicRaiseInterrupt(Buslogic_t *bl, int suppress, uint8_t Interrupt)
|
||||||
{
|
{
|
||||||
#if 0
|
|
||||||
if (bl->Interrupt & INTR_HACC) {
|
|
||||||
pclog("Pending IRQ\n");
|
|
||||||
bl->PendingInterrupt = Interrupt;
|
|
||||||
} else {
|
|
||||||
bl->Interrupt = Interrupt;
|
|
||||||
pclog("Raising IRQ %i\n", bl->Irq);
|
|
||||||
if (bl->IrqEnabled)
|
|
||||||
BuslogicInterrupt(bl, 1);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (Interrupt & (INTR_MBIF | INTR_MBOA))
|
if (Interrupt & (INTR_MBIF | INTR_MBOA))
|
||||||
{
|
{
|
||||||
if (!(bl->Interrupt & INTR_HACC))
|
if (!(bl->Interrupt & INTR_HACC))
|
||||||
@@ -808,8 +800,19 @@ static void
|
|||||||
BuslogicReset(Buslogic_t *bl)
|
BuslogicReset(Buslogic_t *bl)
|
||||||
{
|
{
|
||||||
/* pclog("BuslogicReset()\n"); */
|
/* pclog("BuslogicReset()\n"); */
|
||||||
BuslogicCallback = 0;
|
if (bl->evt)
|
||||||
|
{
|
||||||
|
thread_destroy_event(bl->evt);
|
||||||
|
bl->evt = NULL;
|
||||||
|
if (poll_tid)
|
||||||
|
{
|
||||||
|
poll_tid = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BuslogicResetCallback = 0;
|
BuslogicResetCallback = 0;
|
||||||
|
bl->scan_restart = 0;
|
||||||
|
|
||||||
bl->Geometry = 0x80;
|
bl->Geometry = 0x80;
|
||||||
bl->Status = STAT_IDLE | STAT_INIT;
|
bl->Status = STAT_IDLE | STAT_INIT;
|
||||||
bl->Command = 0xFF;
|
bl->Command = 0xFF;
|
||||||
@@ -822,7 +825,6 @@ BuslogicReset(Buslogic_t *bl)
|
|||||||
bl->MailboxOutInterrupts = 0;
|
bl->MailboxOutInterrupts = 0;
|
||||||
bl->PendingInterrupt = 0;
|
bl->PendingInterrupt = 0;
|
||||||
bl->Lock = 0;
|
bl->Lock = 0;
|
||||||
BuslogicInOperation = 0;
|
|
||||||
|
|
||||||
BuslogicClearInterrupt(bl);
|
BuslogicClearInterrupt(bl);
|
||||||
}
|
}
|
||||||
@@ -876,8 +878,6 @@ BuslogicMailboxInSetup(Buslogic_t *bl, uint32_t CCBPointer, CCBU *CmdBlock,
|
|||||||
req->MailboxCompletionCode = MailboxCompletionCode;
|
req->MailboxCompletionCode = MailboxCompletionCode;
|
||||||
|
|
||||||
pclog("Mailbox in setup\n");
|
pclog("Mailbox in setup\n");
|
||||||
|
|
||||||
BuslogicInOperation = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -934,8 +934,6 @@ BuslogicMailboxIn(Buslogic_t *bl)
|
|||||||
bl->MailboxInPosCur = 0;
|
bl->MailboxInPosCur = 0;
|
||||||
|
|
||||||
BuslogicRaiseInterrupt(bl, 0, INTR_MBIF | INTR_ANY);
|
BuslogicRaiseInterrupt(bl, 0, INTR_MBIF | INTR_ANY);
|
||||||
|
|
||||||
BuslogicInOperation = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1480,8 +1478,13 @@ BuslogicWrite(uint16_t Port, uint8_t Val, void *p)
|
|||||||
if ((Val == 0x02) && (bl->Command == 0xFF)) {
|
if ((Val == 0x02) && (bl->Command == 0xFF)) {
|
||||||
/* If there are no mailboxes configured, don't even try to do anything. */
|
/* If there are no mailboxes configured, don't even try to do anything. */
|
||||||
if (bl->MailboxCount) {
|
if (bl->MailboxCount) {
|
||||||
if (!BuslogicCallback) {
|
if (!poll_tid) {
|
||||||
BuslogicCallback = 1 * TIMER_USEC;
|
pclog("Buslogic: starting thread..\n");
|
||||||
|
poll_tid = thread_create(BuslogicCommandThread, bl);
|
||||||
|
bl->scan_restart = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bl->scan_restart = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -2369,6 +2372,8 @@ BuslogicSCSIRequestSetup(Buslogic_t *bl, uint32_t CCBPointer, Mailbox32_t *Mailb
|
|||||||
fatal("BuslogicSCSIRequestSetup(): Attempting to queue tags\n");
|
fatal("BuslogicSCSIRequestSetup(): Attempting to queue tags\n");
|
||||||
BuslogicMailboxInSetup(bl, CCBPointer, &req->CmdBlock,
|
BuslogicMailboxInSetup(bl, CCBPointer, &req->CmdBlock,
|
||||||
CCB_INVALID_CCB, SCSI_STATUS_OK, MBI_ERROR);
|
CCB_INVALID_CCB, SCSI_STATUS_OK, MBI_ERROR);
|
||||||
|
pclog("BusLogic Callback: Send incoming mailbox\n");
|
||||||
|
BuslogicMailboxIn(bl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2379,6 +2384,9 @@ BuslogicSCSIRequestSetup(Buslogic_t *bl, uint32_t CCBPointer, Mailbox32_t *Mailb
|
|||||||
BuslogicMailboxInSetup(bl, CCBPointer, &req->CmdBlock,
|
BuslogicMailboxInSetup(bl, CCBPointer, &req->CmdBlock,
|
||||||
CCB_INVALID_CCB, SCSI_STATUS_OK, MBI_ERROR);
|
CCB_INVALID_CCB, SCSI_STATUS_OK, MBI_ERROR);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
pclog("BusLogic Callback: Send incoming mailbox\n");
|
||||||
|
BuslogicMailboxIn(bl);
|
||||||
}
|
}
|
||||||
|
|
||||||
pclog("BuslogicSCSIRequestSetup(): Scanning SCSI Target ID %i\n", Id);
|
pclog("BuslogicSCSIRequestSetup(): Scanning SCSI Target ID %i\n", Id);
|
||||||
@@ -2394,6 +2402,9 @@ BuslogicSCSIRequestSetup(Buslogic_t *bl, uint32_t CCBPointer, Mailbox32_t *Mailb
|
|||||||
BuslogicSenseBufferFree(req, 0);
|
BuslogicSenseBufferFree(req, 0);
|
||||||
BuslogicMailboxInSetup(bl, CCBPointer, &req->CmdBlock,
|
BuslogicMailboxInSetup(bl, CCBPointer, &req->CmdBlock,
|
||||||
CCB_SELECTION_TIMEOUT,SCSI_STATUS_OK,MBI_ERROR);
|
CCB_SELECTION_TIMEOUT,SCSI_STATUS_OK,MBI_ERROR);
|
||||||
|
|
||||||
|
pclog("BusLogic Callback: Send incoming mailbox\n");
|
||||||
|
BuslogicMailboxIn(bl);
|
||||||
} else {
|
} else {
|
||||||
pclog("BuslogicSCSIRequestSetup(): SCSI Target ID %i and LUN %i detected and working\n", Id, Lun);
|
pclog("BuslogicSCSIRequestSetup(): SCSI Target ID %i and LUN %i detected and working\n", Id, Lun);
|
||||||
|
|
||||||
@@ -2405,7 +2416,11 @@ BuslogicSCSIRequestSetup(Buslogic_t *bl, uint32_t CCBPointer, Mailbox32_t *Mailb
|
|||||||
req->CmdBlock.common.ControlByte);
|
req->CmdBlock.common.ControlByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuslogicInOperation = 1;
|
pclog("BusLogic Callback: Process SCSI request\n");
|
||||||
|
BuslogicSCSICommand(bl);
|
||||||
|
|
||||||
|
pclog("BusLogic Callback: Send incoming mailbox\n");
|
||||||
|
BuslogicMailboxIn(bl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2421,6 +2436,9 @@ BuslogicSCSIRequestAbort(Buslogic_t *bl, uint32_t CCBPointer)
|
|||||||
/* Only SCSI CD-ROMs are supported at the moment, SCSI hard disk support will come soon. */
|
/* Only SCSI CD-ROMs are supported at the moment, SCSI hard disk support will come soon. */
|
||||||
BuslogicMailboxInSetup(bl, CCBPointer, &CmdBlock,
|
BuslogicMailboxInSetup(bl, CCBPointer, &CmdBlock,
|
||||||
0x26, SCSI_STATUS_OK, MBI_NOT_FOUND);
|
0x26, SCSI_STATUS_OK, MBI_NOT_FOUND);
|
||||||
|
|
||||||
|
pclog("BusLogic Callback: Send incoming mailbox\n");
|
||||||
|
BuslogicMailboxIn(bl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2453,7 +2471,7 @@ BuslogicMailboxOutAdvance(Buslogic_t *bl)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static uint8_t
|
||||||
BuslogicProcessMailbox(Buslogic_t *bl)
|
BuslogicProcessMailbox(Buslogic_t *bl)
|
||||||
{
|
{
|
||||||
Mailbox32_t mb32;
|
Mailbox32_t mb32;
|
||||||
@@ -2467,6 +2485,12 @@ BuslogicProcessMailbox(Buslogic_t *bl)
|
|||||||
pclog("BuslogicProcessMailbox(): Operating in %s mode\n", bl->LocalRAM.structured.autoSCSIData.fAggressiveRoundRobinMode ? "aggressive" : "strict");
|
pclog("BuslogicProcessMailbox(): Operating in %s mode\n", bl->LocalRAM.structured.autoSCSIData.fAggressiveRoundRobinMode ? "aggressive" : "strict");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (bl->Interrupt || bl->PendingInterrupt)
|
||||||
|
{
|
||||||
|
/* pclog("Interrupt set, waiting...\n"); */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* 0 = strict, 1 = aggressive */
|
/* 0 = strict, 1 = aggressive */
|
||||||
if (bl->LocalRAM.structured.autoSCSIData.fAggressiveRoundRobinMode) {
|
if (bl->LocalRAM.structured.autoSCSIData.fAggressiveRoundRobinMode) {
|
||||||
uint8_t MailboxCur = bl->MailboxOutPosCur;
|
uint8_t MailboxCur = bl->MailboxOutPosCur;
|
||||||
@@ -2488,6 +2512,10 @@ BuslogicProcessMailbox(Buslogic_t *bl)
|
|||||||
pclog("BuslogicProcessMailbox(): Writing %i bytes at %08X\n", sizeof(CmdStatus), Outgoing + CodeOffset);
|
pclog("BuslogicProcessMailbox(): Writing %i bytes at %08X\n", sizeof(CmdStatus), Outgoing + CodeOffset);
|
||||||
DMAPageWrite(Outgoing + CodeOffset, (char *)&CmdStatus, sizeof(CmdStatus));
|
DMAPageWrite(Outgoing + CodeOffset, (char *)&CmdStatus, sizeof(CmdStatus));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (bl->MailboxOutInterrupts)
|
if (bl->MailboxOutInterrupts)
|
||||||
BuslogicRaiseInterrupt(bl, 0, INTR_MBOA | INTR_ANY);
|
BuslogicRaiseInterrupt(bl, 0, INTR_MBOA | INTR_ANY);
|
||||||
@@ -2496,11 +2524,6 @@ BuslogicProcessMailbox(Buslogic_t *bl)
|
|||||||
pclog("BuslogicProcessMailbox(): Outgoing mailbox action code: %i\n", mb32.u.out.ActionCode);
|
pclog("BuslogicProcessMailbox(): Outgoing mailbox action code: %i\n", mb32.u.out.ActionCode);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Check if the mailbox is actually loaded. */
|
|
||||||
if (mb32.u.out.ActionCode == MBO_FREE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mb32.u.out.ActionCode == MBO_START) {
|
if (mb32.u.out.ActionCode == MBO_START) {
|
||||||
pclog("Start Mailbox Command\n");
|
pclog("Start Mailbox Command\n");
|
||||||
BuslogicSCSIRequestSetup(bl, mb32.CCBPointer, &mb32);
|
BuslogicSCSIRequestSetup(bl, mb32.CCBPointer, &mb32);
|
||||||
@@ -2514,6 +2537,8 @@ BuslogicProcessMailbox(Buslogic_t *bl)
|
|||||||
/* Advance to the next mailbox. */
|
/* Advance to the next mailbox. */
|
||||||
if (! bl->LocalRAM.structured.autoSCSIData.fAggressiveRoundRobinMode)
|
if (! bl->LocalRAM.structured.autoSCSIData.fAggressiveRoundRobinMode)
|
||||||
BuslogicMailboxOutAdvance(bl);
|
BuslogicMailboxOutAdvance(bl);
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2530,34 +2555,35 @@ BuslogicResetPoll(void *p)
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
BuslogicCommandCallback(void *p)
|
BuslogicCommandThread(void *p)
|
||||||
{
|
{
|
||||||
Buslogic_t *bl = (Buslogic_t *)p;
|
Buslogic_t *bl = (Buslogic_t *)p;
|
||||||
|
|
||||||
if (BuslogicInOperation == 0) {
|
BuslogicEventRestart:
|
||||||
if (bl->MailboxCount) {
|
/* Create a waitable event. */
|
||||||
BuslogicProcessMailbox(bl);
|
bl->evt = thread_create_event();
|
||||||
} else {
|
|
||||||
BuslogicCallback += 1 * TIMER_USEC;
|
BuslogicScanRestart:
|
||||||
return;
|
while (BuslogicProcessMailbox(bl) != 0)
|
||||||
}
|
|
||||||
} else if (BuslogicInOperation == 1) {
|
|
||||||
pclog("BusLogic Callback: Process SCSI request\n");
|
|
||||||
BuslogicSCSICommand(bl);
|
|
||||||
if (bl->Req.CmdBlock.common.Cdb[0] == 0x42)
|
|
||||||
{
|
{
|
||||||
/* This is needed since CD Audio inevitably means READ SUBCHANNEL spam. */
|
|
||||||
BuslogicCallback += 1000 * TIMER_USEC;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (BuslogicInOperation == 2) {
|
|
||||||
pclog("BusLogic Callback: Send incoming mailbox\n");
|
|
||||||
BuslogicMailboxIn(bl);
|
|
||||||
} else {
|
|
||||||
fatal("Invalid BusLogic callback phase: %i\n", BuslogicInOperation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BuslogicCallback += 1 * TIMER_USEC;
|
if (bl->scan_restart)
|
||||||
|
{
|
||||||
|
goto BuslogicScanRestart;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_destroy_event(bl->evt);
|
||||||
|
bl->evt = NULL;
|
||||||
|
|
||||||
|
if (bl->scan_restart)
|
||||||
|
{
|
||||||
|
goto BuslogicEventRestart;
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_tid = NULL;
|
||||||
|
|
||||||
|
pclog("Buslogic: polling stopped.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2825,7 +2851,6 @@ BuslogicInit(int chip)
|
|||||||
bl = malloc(sizeof(Buslogic_t));
|
bl = malloc(sizeof(Buslogic_t));
|
||||||
memset(bl, 0x00, sizeof(Buslogic_t));
|
memset(bl, 0x00, sizeof(Buslogic_t));
|
||||||
|
|
||||||
BuslogicResetDevice = bl;
|
|
||||||
if (!PCI && (chip == CHIP_BUSLOGIC_PCI))
|
if (!PCI && (chip == CHIP_BUSLOGIC_PCI))
|
||||||
{
|
{
|
||||||
chip = CHIP_BUSLOGIC_ISA;
|
chip = CHIP_BUSLOGIC_ISA;
|
||||||
@@ -2923,8 +2948,6 @@ BuslogicInit(int chip)
|
|||||||
|
|
||||||
timer_add(BuslogicResetPoll,
|
timer_add(BuslogicResetPoll,
|
||||||
&BuslogicResetCallback, &BuslogicResetCallback, bl);
|
&BuslogicResetCallback, &BuslogicResetCallback, bl);
|
||||||
timer_add(BuslogicCommandCallback,
|
|
||||||
&BuslogicCallback, &BuslogicCallback, bl);
|
|
||||||
|
|
||||||
if (bl->chip == CHIP_BUSLOGIC_PCI) {
|
if (bl->chip == CHIP_BUSLOGIC_PCI) {
|
||||||
bl->Card = pci_add_card(PCI_ADD_NORMAL, BuslogicPCIRead, BuslogicPCIWrite, bl);
|
bl->Card = pci_add_card(PCI_ADD_NORMAL, BuslogicPCIRead, BuslogicPCIWrite, bl);
|
||||||
@@ -2984,8 +3007,22 @@ static void
|
|||||||
BuslogicClose(void *p)
|
BuslogicClose(void *p)
|
||||||
{
|
{
|
||||||
Buslogic_t *bl = (Buslogic_t *)p;
|
Buslogic_t *bl = (Buslogic_t *)p;
|
||||||
|
if (bl)
|
||||||
|
{
|
||||||
|
if (bl->evt)
|
||||||
|
{
|
||||||
|
thread_destroy_event(bl->evt);
|
||||||
|
bl->evt = NULL;
|
||||||
|
|
||||||
|
if (poll_tid)
|
||||||
|
{
|
||||||
|
poll_tid = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
free(bl);
|
free(bl);
|
||||||
BuslogicResetDevice = NULL;
|
bl = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user