blob: 0f55bb98d591bbbe66384839df02655d9be07d53 [file] [log] [blame]
Paolo Bonzini7a39e722012-08-06 13:15:06 +02001// AMD PCscsi boot support.
2//
3// Copyright (C) 2012 Red Hat Inc.
4//
5// Authors:
6// Paolo Bonzini <pbonzini@redhat.com>
7//
8// based on lsi-scsi.c which is written by:
9// Gerd Hoffman <kraxel@redhat.com>
10//
11// This file may be distributed under the terms of the GNU LGPLv3 license.
12
13#include "util.h" // dprintf
14#include "pci.h" // foreachpci
15#include "config.h" // CONFIG_*
16#include "biosvar.h" // GET_GLOBAL
17#include "pci_ids.h" // PCI_DEVICE_ID
18#include "pci_regs.h" // PCI_VENDOR_ID
19#include "boot.h" // bootprio_find_scsi_device
Kevin O'Connord83c87b2013-01-21 01:14:12 -050020#include "blockcmd.h" // scsi_drive_setup
Kevin O'Connorccee6e82013-09-02 21:25:21 -040021#include "fw/paravirt.h" // runningOnQEMU
Paolo Bonzini7a39e722012-08-06 13:15:06 +020022#include "disk.h"
Kevin O'Connor9dea5902013-09-14 20:23:54 -040023#include "malloc.h" // free
Kevin O'Connorfa9c66a2013-09-14 19:10:40 -040024#include "string.h" // memset
Paolo Bonzini7a39e722012-08-06 13:15:06 +020025
26#define ESP_TCLO 0x00
27#define ESP_TCMID 0x04
28#define ESP_FIFO 0x08
29#define ESP_CMD 0x0c
30#define ESP_WBUSID 0x10
31#define ESP_TCHI 0x38
32
33#define ESP_RSTAT 0x10
34#define ESP_RINTR 0x14
35#define ESP_RFLAGS 0x1c
36
37#define ESP_DMA_CMD 0x40
38#define ESP_DMA_STC 0x44
39#define ESP_DMA_SPA 0x48
40#define ESP_DMA_WBC 0x4c
41#define ESP_DMA_WAC 0x50
42#define ESP_DMA_STAT 0x54
43#define ESP_DMA_SMDLA 0x58
44#define ESP_DMA_WMAC 0x58c
45
46#define ESP_CMD_DMA 0x80
47#define ESP_CMD_RESET 0x02
48#define ESP_CMD_TI 0x10
49#define ESP_CMD_ICCS 0x11
50#define ESP_CMD_SELATN 0x42
51
52#define ESP_STAT_DI 0x01
53#define ESP_STAT_CD 0x02
54#define ESP_STAT_MSG 0x04
55#define ESP_STAT_TC 0x10
56
57#define ESP_INTR_DC 0x20
58
59struct esp_lun_s {
60 struct drive_s drive;
61 struct pci_device *pci;
62 u32 iobase;
63 u8 target;
64 u8 lun;
65};
66
67static void
68esp_scsi_dma(u32 iobase, u32 buf, u32 len, int read)
69{
70 outb(len & 0xff, iobase + ESP_TCLO);
71 outb((len >> 8) & 0xff, iobase + ESP_TCMID);
72 outb((len >> 16) & 0xff, iobase + ESP_TCHI);
73 outl(buf, iobase + ESP_DMA_SPA);
74 outl(len, iobase + ESP_DMA_STC);
75 outb(read ? 0x83 : 0x03, iobase + ESP_DMA_CMD);
76}
77
78static int
79esp_scsi_cmd(struct esp_lun_s *llun, struct disk_op_s *op,
80 u8 *cdbcmd, u16 target, u16 lun, u16 blocksize)
81{
82 u32 iobase = GET_GLOBAL(llun->iobase);
83 int i, state;
84 u8 status;
85
86 outb(target, iobase + ESP_WBUSID);
87
88 /*
89 * We need to pass the LUN at the beginning of the command, and the FIFO
90 * is only 16 bytes, so we cannot support 16-byte CDBs. The alternative
91 * would be to use DMA for the 17-byte command too, which is quite
92 * overkill.
93 */
94 outb(lun, iobase + ESP_FIFO);
95 cdbcmd[1] &= 0x1f;
96 cdbcmd[1] |= lun << 5;
97 for (i = 0; i < 12; i++)
98 outb(cdbcmd[i], iobase + ESP_FIFO);
99 outb(ESP_CMD_SELATN, iobase + ESP_CMD);
100
101 for (state = 0;;) {
102 u8 stat = inb(iobase + ESP_RSTAT);
103
104 /* Detect disconnected device. */
105 if (state == 0 && (inb(iobase + ESP_RINTR) & ESP_INTR_DC)) {
106 return DISK_RET_ENOTREADY;
107 }
108
109 /* HBA reads command, clears CD, sets TC -> do DMA if needed. */
110 if (state == 0 && (stat & ESP_STAT_TC)) {
111 state++;
112 if (op->count && blocksize) {
113 /* Data phase. */
114 u32 count = (u32)op->count * blocksize;
115 esp_scsi_dma(iobase, (u32)op->buf_fl, count,
116 cdb_is_read(cdbcmd, blocksize));
117 outb(ESP_CMD_TI | ESP_CMD_DMA, iobase + ESP_CMD);
118 continue;
119 }
120 }
121
122 /* At end of DMA TC is set again -> complete command. */
123 if (state == 1 && (stat & ESP_STAT_TC)) {
124 state++;
125 outb(ESP_CMD_ICCS, iobase + ESP_CMD);
126 continue;
127 }
128
129 /* Finally read data from the message in phase. */
130 if (state == 2 && (stat & ESP_STAT_MSG)) {
131 state++;
132 status = inb(iobase + ESP_FIFO);
133 inb(iobase + ESP_FIFO);
134 break;
135 }
136 usleep(5);
137 }
138
139 if (status == 0) {
140 return DISK_RET_SUCCESS;
141 }
142
143 return DISK_RET_EBADTRACK;
144}
145
146int
147esp_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
148{
149 if (!CONFIG_ESP_SCSI)
150 return DISK_RET_EBADTRACK;
151
152 struct esp_lun_s *llun =
153 container_of(op->drive_g, struct esp_lun_s, drive);
154
155 return esp_scsi_cmd(llun, op, cdbcmd,
156 GET_GLOBAL(llun->target), GET_GLOBAL(llun->lun),
157 blocksize);
158}
159
160static int
161esp_scsi_add_lun(struct pci_device *pci, u32 iobase, u8 target, u8 lun)
162{
163 struct esp_lun_s *llun = malloc_fseg(sizeof(*llun));
164 if (!llun) {
165 warn_noalloc();
166 return -1;
167 }
168 memset(llun, 0, sizeof(*llun));
169 llun->drive.type = DTYPE_ESP_SCSI;
170 llun->drive.cntl_id = pci->bdf;
171 llun->pci = pci;
172 llun->target = target;
173 llun->lun = lun;
174 llun->iobase = iobase;
175
176 char *name = znprintf(16, "esp %02x:%02x.%x %d:%d",
177 pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf),
178 pci_bdf_to_fn(pci->bdf), target, lun);
179 int prio = bootprio_find_scsi_device(pci, target, lun);
Kevin O'Connord83c87b2013-01-21 01:14:12 -0500180 int ret = scsi_drive_setup(&llun->drive, name, prio);
Paolo Bonzini7a39e722012-08-06 13:15:06 +0200181 free(name);
182 if (ret)
183 goto fail;
184 return 0;
185
186fail:
187 free(llun);
188 return -1;
189}
190
191static void
192esp_scsi_scan_target(struct pci_device *pci, u32 iobase, u8 target)
193{
194 esp_scsi_add_lun(pci, iobase, target, 0);
195}
196
197static void
198init_esp_scsi(struct pci_device *pci)
199{
200 u16 bdf = pci->bdf;
201 u32 iobase = pci_config_readl(pci->bdf, PCI_BASE_ADDRESS_0)
202 & PCI_BASE_ADDRESS_IO_MASK;
203
204 dprintf(1, "found esp at %02x:%02x.%x, io @ %x\n",
205 pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf),
206 pci_bdf_to_fn(bdf), iobase);
207
Paolo Bonzini68513ab2012-11-20 18:33:41 +0100208 pci_config_maskw(bdf, PCI_COMMAND, 0, PCI_COMMAND_MASTER);
209
Paolo Bonzini7a39e722012-08-06 13:15:06 +0200210 // reset
211 outb(ESP_CMD_RESET, iobase + ESP_CMD);
212
213 int i;
214 for (i = 0; i <= 7; i++)
215 esp_scsi_scan_target(pci, iobase, i);
216
217 return;
218}
219
220void
221esp_scsi_setup(void)
222{
223 ASSERT32FLAT();
Kevin O'Connor897fb112013-02-07 23:32:48 -0500224 if (!CONFIG_ESP_SCSI || !runningOnQEMU())
Paolo Bonzini7a39e722012-08-06 13:15:06 +0200225 return;
226
227 dprintf(3, "init esp\n");
228
229 struct pci_device *pci;
230 foreachpci(pci) {
231 if (pci->vendor != PCI_VENDOR_ID_AMD
232 || pci->device != PCI_DEVICE_ID_AMD_SCSI)
233 continue;
234 init_esp_scsi(pci);
235 }
236}