blob: ebc49f92ae7ed1aecf47d8a3ca19660a0355607a [file] [log] [blame]
Hannes Reinecke2df70bf2012-11-13 15:03:31 +01001// MegaRAID SAS boot support.
2//
3// Copyright (C) 2012 Hannes Reinecke, SUSE Linux Products GmbH
4//
5// Authors:
6// Hannes Reinecke <hare@suse.de>
7//
8// based on virtio-scsi.c which is written by:
9// Paolo Bonzini <pbonzini@redhat.com>
10//
11// This file may be distributed under the terms of the GNU LGPLv3 license.
12
Hannes Reinecke2df70bf2012-11-13 15:03:31 +010013#include "biosvar.h" // GET_GLOBAL
Kevin O'Connor135f3f62013-09-14 23:57:26 -040014#include "block.h" // struct drive_s
Kevin O'Connord83c87b2013-01-21 01:14:12 -050015#include "blockcmd.h" // scsi_drive_setup
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040016#include "config.h" // CONFIG_*
Kevin O'Connor9dea5902013-09-14 20:23:54 -040017#include "malloc.h" // free
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040018#include "output.h" // dprintf
19#include "pci.h" // foreachpci
20#include "pci_ids.h" // PCI_DEVICE_ID_XXX
21#include "pci_regs.h" // PCI_VENDOR_ID
Kevin O'Connor3df600b2013-09-14 19:28:55 -040022#include "stacks.h" // yield
Kevin O'Connor135f3f62013-09-14 23:57:26 -040023#include "std/disk.h" // DISK_RET_SUCCESS
Kevin O'Connorfa9c66a2013-09-14 19:10:40 -040024#include "string.h" // memset
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040025#include "util.h" // timer_calc
Hannes Reinecke2df70bf2012-11-13 15:03:31 +010026
27#define MFI_DB 0x0 // Doorbell
28#define MFI_OMSG0 0x18 // Outbound message 0
29#define MFI_IDB 0x20 // Inbound doorbell
30#define MFI_ODB 0x2c // Outbound doorbell
31#define MFI_IQP 0x40 // Inbound queue port
32#define MFI_OSP0 0xb0 // Outbound scratch pad0
33#define MFI_IQPL 0xc0 // Inbound queue port (low bytes)
34#define MFI_IQPH 0xc4 // Inbound queue port (high bytes)
35
36#define MFI_STATE_MASK 0xf0000000
37#define MFI_STATE_WAIT_HANDSHAKE 0x60000000
38#define MFI_STATE_BOOT_MESSAGE_PENDING 0x90000000
39#define MFI_STATE_READY 0xb0000000
40#define MFI_STATE_OPERATIONAL 0xc0000000
41#define MFI_STATE_FAULT 0xf0000000
42
43/* MFI Commands */
44typedef enum {
45 MFI_CMD_INIT = 0x00,
46 MFI_CMD_LD_READ,
47 MFI_CMD_LD_WRITE,
48 MFI_CMD_LD_SCSI_IO,
49 MFI_CMD_PD_SCSI_IO,
50 MFI_CMD_DCMD,
51 MFI_CMD_ABORT,
52 MFI_CMD_SMP,
53 MFI_CMD_STP
54} mfi_cmd_t;
55
56struct megasas_cmd_frame {
57 u8 cmd; /*00h */
58 u8 sense_len; /*01h */
59 u8 cmd_status; /*02h */
60 u8 scsi_status; /*03h */
61
62 u8 target_id; /*04h */
63 u8 lun; /*05h */
64 u8 cdb_len; /*06h */
65 u8 sge_count; /*07h */
66
67 u32 context; /*08h */
68 u32 context_64; /*0Ch */
69
70 u16 flags; /*10h */
71 u16 timeout; /*12h */
72 u32 data_xfer_len; /*14h */
73
74 union {
75 struct {
76 u32 opcode; /*18h */
77 u8 mbox[12]; /*1Ch */
78 u32 sgl_addr; /*28h */
79 u32 sgl_len; /*32h */
80 u32 pad; /*34h */
81 } dcmd;
82 struct {
83 u32 sense_buf_lo; /*18h */
84 u32 sense_buf_hi; /*1Ch */
85 u8 cdb[16]; /*20h */
86 u32 sgl_addr; /*30h */
87 u32 sgl_len; /*34h */
88 } pthru;
89 struct {
90 u8 pad[22]; /*18h */
91 } gen;
92 };
93} __attribute__ ((packed));
94
95struct mfi_ld_list_s {
96 u32 count;
97 u32 reserved_0;
98 struct {
99 u8 target;
100 u8 lun;
101 u16 seq;
102 u8 state;
103 u8 reserved_1[3];
104 u64 size;
105 } lds[64];
106} __attribute__ ((packed));
107
108#define MEGASAS_POLL_TIMEOUT 60000 // 60 seconds polling timeout
109
110struct megasas_lun_s {
111 struct drive_s drive;
112 struct pci_device *pci;
113 struct megasas_cmd_frame *frame;
114 u32 iobase;
115 u8 target;
116 u8 lun;
117};
118
119static int megasas_fire_cmd(u16 pci_id, u32 ioaddr,
120 struct megasas_cmd_frame *frame)
121{
122 u32 frame_addr = (u32)frame;
123 int frame_count = 1;
124 u8 cmd_state;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100125
126 dprintf(2, "Frame 0x%x\n", frame_addr);
127 if (pci_id == PCI_DEVICE_ID_LSI_SAS2004 ||
128 pci_id == PCI_DEVICE_ID_LSI_SAS2008) {
129 outl(0, ioaddr + MFI_IQPH);
130 outl(frame_addr | frame_count << 1 | 1, ioaddr + MFI_IQPL);
131 } else if (pci_id == PCI_DEVICE_ID_DELL_PERC5 ||
132 pci_id == PCI_DEVICE_ID_LSI_SAS1064R ||
133 pci_id == PCI_DEVICE_ID_LSI_VERDE_ZCR) {
134 outl(frame_addr >> 3 | frame_count, ioaddr + MFI_IQP);
135 } else {
136 outl(frame_addr | frame_count << 1 | 1, ioaddr + MFI_IQP);
137 }
138
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400139 u32 end = timer_calc(MEGASAS_POLL_TIMEOUT);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100140 do {
141 for (;;) {
142 cmd_state = GET_LOWFLAT(frame->cmd_status);
143 if (cmd_state != 0xff)
144 break;
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400145 if (timer_check(end)) {
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100146 warn_timeout();
147 return -1;
148 }
149 yield();
150 }
151 } while (cmd_state == 0xff);
152
153 if (cmd_state == 0 || cmd_state == 0x2d)
154 return 0;
155 dprintf(1, "ERROR: Frame 0x%x, status 0x%x\n", frame_addr, cmd_state);
156 return -1;
157}
158
159int
160megasas_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
161{
162 struct megasas_lun_s *mlun =
163 container_of(op->drive_g, struct megasas_lun_s, drive);
164 u8 *cdb = cdbcmd;
165 struct megasas_cmd_frame *frame = GET_GLOBAL(mlun->frame);
166 u16 pci_id = GET_GLOBAL(mlun->pci->device);
167 int i;
168
169 if (!CONFIG_MEGASAS)
170 return DISK_RET_EBADTRACK;
171
172 memset_fl(frame, 0, sizeof(*frame));
173 SET_LOWFLAT(frame->cmd, MFI_CMD_LD_SCSI_IO);
174 SET_LOWFLAT(frame->cmd_status, 0xFF);
175 SET_LOWFLAT(frame->target_id, GET_GLOBAL(mlun->target));
176 SET_LOWFLAT(frame->lun, GET_GLOBAL(mlun->lun));
177 SET_LOWFLAT(frame->flags, 0x0001);
178 SET_LOWFLAT(frame->data_xfer_len, op->count * blocksize);
179 SET_LOWFLAT(frame->cdb_len, 16);
180
181 for (i = 0; i < 16; i++) {
182 SET_LOWFLAT(frame->pthru.cdb[i], cdb[i]);
183 }
184 dprintf(2, "pthru cmd 0x%x count %d bs %d\n",
185 cdb[0], op->count, blocksize);
186
187 if (op->count) {
188 SET_LOWFLAT(frame->pthru.sgl_addr, (u32)op->buf_fl);
189 SET_LOWFLAT(frame->pthru.sgl_len, op->count * blocksize);
190 SET_LOWFLAT(frame->sge_count, 1);
191 }
192 SET_LOWFLAT(frame->context, (u32)frame);
193
194 if (megasas_fire_cmd(pci_id, GET_GLOBAL(mlun->iobase), frame) == 0)
195 return DISK_RET_SUCCESS;
196
197 dprintf(2, "pthru cmd 0x%x failed\n", cdb[0]);
198 return DISK_RET_EBADTRACK;
199}
200
201static int
202megasas_add_lun(struct pci_device *pci, u32 iobase, u8 target, u8 lun)
203{
204 struct megasas_lun_s *mlun = malloc_fseg(sizeof(*mlun));
205 char *name;
206 int prio, ret = 0;
207
208 if (!mlun) {
209 warn_noalloc();
210 return -1;
211 }
212 memset(mlun, 0, sizeof(*mlun));
213 mlun->drive.type = DTYPE_MEGASAS;
214 mlun->drive.cntl_id = pci->bdf;
215 mlun->pci = pci;
216 mlun->target = target;
217 mlun->lun = lun;
218 mlun->iobase = iobase;
219 mlun->frame = memalign_low(256, sizeof(struct megasas_cmd_frame));
220 if (!mlun->frame) {
221 warn_noalloc();
222 free(mlun);
223 return -1;
224 }
225 name = znprintf(36, "MegaRAID SAS (PCI %02x:%02x.%x) LD %d:%d",
226 pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf),
227 pci_bdf_to_fn(pci->bdf), target, lun);
228 prio = bootprio_find_scsi_device(pci, target, lun);
Kevin O'Connord83c87b2013-01-21 01:14:12 -0500229 ret = scsi_drive_setup(&mlun->drive, name, prio);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100230 free(name);
231 if (ret) {
232 free(mlun->frame);
233 free(mlun);
234 ret = -1;
235 }
236
237 return ret;
238}
239
240static void megasas_scan_target(struct pci_device *pci, u32 iobase)
241{
242 struct mfi_ld_list_s ld_list;
243 struct megasas_cmd_frame *frame = memalign_tmp(256, sizeof(*frame));
244 int i;
245
246 memset(&ld_list, 0, sizeof(ld_list));
247 memset_fl(frame, 0, sizeof(*frame));
248
249 frame->cmd = MFI_CMD_DCMD;
250 frame->cmd_status = 0xFF;
251 frame->sge_count = 1;
252 frame->flags = 0x0011;
253 frame->data_xfer_len = sizeof(ld_list);
254 frame->dcmd.opcode = 0x03010000;
255 frame->dcmd.sgl_addr = (u32)MAKE_FLATPTR(GET_SEG(SS), &ld_list);
256 frame->dcmd.sgl_len = sizeof(ld_list);
257 frame->context = (u32)frame;
258
259 if (megasas_fire_cmd(pci->device, iobase, frame) == 0) {
260 dprintf(2, "%d LD found\n", ld_list.count);
261 for (i = 0; i < ld_list.count; i++) {
262 dprintf(2, "LD %d:%d state 0x%x\n",
263 ld_list.lds[i].target, ld_list.lds[i].lun,
264 ld_list.lds[i].state);
265 if (ld_list.lds[i].state != 0) {
266 megasas_add_lun(pci, iobase,
267 ld_list.lds[i].target, ld_list.lds[i].lun);
268 }
269 }
270 }
271}
272
273static int megasas_transition_to_ready(struct pci_device *pci, u32 ioaddr)
274{
275 u32 fw_state = 0, new_state, mfi_flags = 0;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100276
277 if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
278 pci->device == PCI_DEVICE_ID_DELL_PERC5)
279 new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK;
280 else
281 new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK;
282
283 while (fw_state != new_state) {
284 switch (new_state) {
285 case MFI_STATE_FAULT:
286 dprintf(1, "ERROR: fw in fault state\n");
287 return -1;
288 break;
289 case MFI_STATE_WAIT_HANDSHAKE:
290 mfi_flags = 0x08;
291 /* fallthrough */
292 case MFI_STATE_BOOT_MESSAGE_PENDING:
293 mfi_flags |= 0x10;
294 if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
295 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
296 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
297 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
298 outl(ioaddr + MFI_DB, mfi_flags);
299 } else {
300 outl(ioaddr + MFI_IDB, mfi_flags);
301 }
302 break;
303 case MFI_STATE_OPERATIONAL:
304 mfi_flags = 0x07;
305 if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
306 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
307 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
308 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
309 outl(ioaddr + MFI_DB, mfi_flags);
310 if (pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
311 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
312 int j = 0;
313 u32 doorbell;
314
315 while (j < MEGASAS_POLL_TIMEOUT) {
316 doorbell = inl(ioaddr + MFI_DB) & 1;
317 if (!doorbell)
318 break;
319 msleep(20);
320 j++;
321 }
322 }
323 } else {
324 outw(ioaddr + MFI_IDB, mfi_flags);
325 }
326 break;
327 case MFI_STATE_READY:
328 dprintf(2, "MegaRAID SAS fw ready\n");
329 return 0;
330 }
331 // The current state should not last longer than poll timeout
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400332 u32 end = timer_calc(MEGASAS_POLL_TIMEOUT);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100333 for (;;) {
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400334 if (timer_check(end)) {
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100335 break;
336 }
337 yield();
338 fw_state = new_state;
339 if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
340 pci->device == PCI_DEVICE_ID_DELL_PERC5)
341 new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK;
342 else
343 new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK;
344 if (new_state != fw_state) {
345 break;
346 }
347 }
348 }
349 dprintf(1, "ERROR: fw in state %x\n", new_state & MFI_STATE_MASK);
350 return -1;
351}
352
353static void
354init_megasas(struct pci_device *pci)
355{
356 u16 bdf = pci->bdf;
357 u32 iobase = pci_config_readl(pci->bdf, PCI_BASE_ADDRESS_2)
358 & PCI_BASE_ADDRESS_IO_MASK;
359
360 dprintf(1, "found MegaRAID SAS at %02x:%02x.%x, io @ %x\n",
361 pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf),
362 pci_bdf_to_fn(bdf), iobase);
363
364 pci_config_maskw(pci->bdf, PCI_COMMAND, 0,
365 PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
366 // reset
367 if (megasas_transition_to_ready(pci, iobase) == 0)
368 megasas_scan_target(pci, iobase);
369
370 return;
371}
372
373void
374megasas_setup(void)
375{
376 ASSERT32FLAT();
377 if (!CONFIG_MEGASAS)
378 return;
379
380 dprintf(3, "init megasas\n");
381
382 struct pci_device *pci;
383 foreachpci(pci) {
384 if (pci->vendor != PCI_VENDOR_ID_LSI_LOGIC &&
385 pci->vendor != PCI_VENDOR_ID_DELL)
386 continue;
Hannes Reinecke261e8702012-12-19 15:12:47 +0100387 if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
388 pci->device == PCI_DEVICE_ID_LSI_SAS1078 ||
389 pci->device == PCI_DEVICE_ID_LSI_SAS1078DE ||
390 pci->device == PCI_DEVICE_ID_LSI_SAS2108 ||
391 pci->device == PCI_DEVICE_ID_LSI_SAS2108E ||
392 pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
393 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
394 pci->device == PCI_DEVICE_ID_LSI_VERDE_ZCR ||
395 pci->device == PCI_DEVICE_ID_DELL_PERC5 ||
396 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
397 pci->device == PCI_DEVICE_ID_LSI_SAS3108)
398 init_megasas(pci);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100399 }
400}