blob: efd0f6ef16bc7a5a5b3098c865266664754e64fc [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
Kevin O'Connor1902c942013-10-26 11:48:06 -040013#include "biosvar.h" // GET_GLOBALFLAT
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
Kevin O'Connor4d8510c2016-02-03 01:28:20 -050019#include "pci.h" // pci_config_readl
20#include "pcidevice.h" // foreachpci
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040021#include "pci_ids.h" // PCI_DEVICE_ID_XXX
22#include "pci_regs.h" // PCI_VENDOR_ID
Kevin O'Connor3df600b2013-09-14 19:28:55 -040023#include "stacks.h" // yield
Kevin O'Connor135f3f62013-09-14 23:57:26 -040024#include "std/disk.h" // DISK_RET_SUCCESS
Kevin O'Connorfa9c66a2013-09-14 19:10:40 -040025#include "string.h" // memset
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040026#include "util.h" // timer_calc
Hannes Reinecke2df70bf2012-11-13 15:03:31 +010027
28#define MFI_DB 0x0 // Doorbell
29#define MFI_OMSG0 0x18 // Outbound message 0
30#define MFI_IDB 0x20 // Inbound doorbell
31#define MFI_ODB 0x2c // Outbound doorbell
32#define MFI_IQP 0x40 // Inbound queue port
33#define MFI_OSP0 0xb0 // Outbound scratch pad0
34#define MFI_IQPL 0xc0 // Inbound queue port (low bytes)
35#define MFI_IQPH 0xc4 // Inbound queue port (high bytes)
36
37#define MFI_STATE_MASK 0xf0000000
38#define MFI_STATE_WAIT_HANDSHAKE 0x60000000
39#define MFI_STATE_BOOT_MESSAGE_PENDING 0x90000000
40#define MFI_STATE_READY 0xb0000000
41#define MFI_STATE_OPERATIONAL 0xc0000000
42#define MFI_STATE_FAULT 0xf0000000
43
44/* MFI Commands */
45typedef enum {
46 MFI_CMD_INIT = 0x00,
47 MFI_CMD_LD_READ,
48 MFI_CMD_LD_WRITE,
49 MFI_CMD_LD_SCSI_IO,
50 MFI_CMD_PD_SCSI_IO,
51 MFI_CMD_DCMD,
52 MFI_CMD_ABORT,
53 MFI_CMD_SMP,
54 MFI_CMD_STP
55} mfi_cmd_t;
56
57struct megasas_cmd_frame {
58 u8 cmd; /*00h */
59 u8 sense_len; /*01h */
60 u8 cmd_status; /*02h */
61 u8 scsi_status; /*03h */
62
63 u8 target_id; /*04h */
64 u8 lun; /*05h */
65 u8 cdb_len; /*06h */
66 u8 sge_count; /*07h */
67
68 u32 context; /*08h */
69 u32 context_64; /*0Ch */
70
71 u16 flags; /*10h */
72 u16 timeout; /*12h */
73 u32 data_xfer_len; /*14h */
74
75 union {
76 struct {
77 u32 opcode; /*18h */
78 u8 mbox[12]; /*1Ch */
79 u32 sgl_addr; /*28h */
80 u32 sgl_len; /*32h */
81 u32 pad; /*34h */
82 } dcmd;
83 struct {
84 u32 sense_buf_lo; /*18h */
85 u32 sense_buf_hi; /*1Ch */
86 u8 cdb[16]; /*20h */
87 u32 sgl_addr; /*30h */
88 u32 sgl_len; /*34h */
89 } pthru;
90 struct {
91 u8 pad[22]; /*18h */
92 } gen;
93 };
94} __attribute__ ((packed));
95
96struct mfi_ld_list_s {
97 u32 count;
98 u32 reserved_0;
99 struct {
100 u8 target;
101 u8 lun;
102 u16 seq;
103 u8 state;
104 u8 reserved_1[3];
105 u64 size;
106 } lds[64];
107} __attribute__ ((packed));
108
109#define MEGASAS_POLL_TIMEOUT 60000 // 60 seconds polling timeout
110
111struct megasas_lun_s {
112 struct drive_s drive;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100113 struct megasas_cmd_frame *frame;
114 u32 iobase;
Kevin O'Connor051275b2013-10-26 11:53:38 -0400115 u16 pci_id;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100116 u8 target;
117 u8 lun;
118};
119
120static int megasas_fire_cmd(u16 pci_id, u32 ioaddr,
121 struct megasas_cmd_frame *frame)
122{
123 u32 frame_addr = (u32)frame;
124 int frame_count = 1;
125 u8 cmd_state;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100126
127 dprintf(2, "Frame 0x%x\n", frame_addr);
128 if (pci_id == PCI_DEVICE_ID_LSI_SAS2004 ||
129 pci_id == PCI_DEVICE_ID_LSI_SAS2008) {
130 outl(0, ioaddr + MFI_IQPH);
131 outl(frame_addr | frame_count << 1 | 1, ioaddr + MFI_IQPL);
132 } else if (pci_id == PCI_DEVICE_ID_DELL_PERC5 ||
133 pci_id == PCI_DEVICE_ID_LSI_SAS1064R ||
134 pci_id == PCI_DEVICE_ID_LSI_VERDE_ZCR) {
135 outl(frame_addr >> 3 | frame_count, ioaddr + MFI_IQP);
136 } else {
137 outl(frame_addr | frame_count << 1 | 1, ioaddr + MFI_IQP);
138 }
139
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400140 u32 end = timer_calc(MEGASAS_POLL_TIMEOUT);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100141 do {
142 for (;;) {
143 cmd_state = GET_LOWFLAT(frame->cmd_status);
144 if (cmd_state != 0xff)
145 break;
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400146 if (timer_check(end)) {
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100147 warn_timeout();
148 return -1;
149 }
150 yield();
151 }
152 } while (cmd_state == 0xff);
153
154 if (cmd_state == 0 || cmd_state == 0x2d)
155 return 0;
156 dprintf(1, "ERROR: Frame 0x%x, status 0x%x\n", frame_addr, cmd_state);
157 return -1;
158}
159
160int
Kevin O'Connor0429a9e2015-07-07 12:06:01 -0400161megasas_process_op(struct disk_op_s *op)
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100162{
Kevin O'Connor0429a9e2015-07-07 12:06:01 -0400163 if (!CONFIG_MEGASAS)
164 return DISK_RET_EBADTRACK;
165 u8 cdb[16];
166 int blocksize = scsi_fill_cmd(op, cdb, sizeof(cdb));
167 if (blocksize < 0)
168 return default_process_op(op);
Kevin O'Connor1902c942013-10-26 11:48:06 -0400169 struct megasas_lun_s *mlun_gf =
170 container_of(op->drive_gf, struct megasas_lun_s, drive);
Kevin O'Connor1902c942013-10-26 11:48:06 -0400171 struct megasas_cmd_frame *frame = GET_GLOBALFLAT(mlun_gf->frame);
Kevin O'Connor051275b2013-10-26 11:53:38 -0400172 u16 pci_id = GET_GLOBALFLAT(mlun_gf->pci_id);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100173 int i;
174
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100175 memset_fl(frame, 0, sizeof(*frame));
176 SET_LOWFLAT(frame->cmd, MFI_CMD_LD_SCSI_IO);
177 SET_LOWFLAT(frame->cmd_status, 0xFF);
Kevin O'Connor1902c942013-10-26 11:48:06 -0400178 SET_LOWFLAT(frame->target_id, GET_GLOBALFLAT(mlun_gf->target));
179 SET_LOWFLAT(frame->lun, GET_GLOBALFLAT(mlun_gf->lun));
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100180 SET_LOWFLAT(frame->flags, 0x0001);
181 SET_LOWFLAT(frame->data_xfer_len, op->count * blocksize);
182 SET_LOWFLAT(frame->cdb_len, 16);
183
184 for (i = 0; i < 16; i++) {
185 SET_LOWFLAT(frame->pthru.cdb[i], cdb[i]);
186 }
187 dprintf(2, "pthru cmd 0x%x count %d bs %d\n",
188 cdb[0], op->count, blocksize);
189
190 if (op->count) {
191 SET_LOWFLAT(frame->pthru.sgl_addr, (u32)op->buf_fl);
192 SET_LOWFLAT(frame->pthru.sgl_len, op->count * blocksize);
193 SET_LOWFLAT(frame->sge_count, 1);
194 }
195 SET_LOWFLAT(frame->context, (u32)frame);
196
Kevin O'Connor1902c942013-10-26 11:48:06 -0400197 if (megasas_fire_cmd(pci_id, GET_GLOBALFLAT(mlun_gf->iobase), frame) == 0)
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100198 return DISK_RET_SUCCESS;
199
200 dprintf(2, "pthru cmd 0x%x failed\n", cdb[0]);
201 return DISK_RET_EBADTRACK;
202}
203
204static int
205megasas_add_lun(struct pci_device *pci, u32 iobase, u8 target, u8 lun)
206{
207 struct megasas_lun_s *mlun = malloc_fseg(sizeof(*mlun));
208 char *name;
209 int prio, ret = 0;
210
211 if (!mlun) {
212 warn_noalloc();
213 return -1;
214 }
215 memset(mlun, 0, sizeof(*mlun));
216 mlun->drive.type = DTYPE_MEGASAS;
217 mlun->drive.cntl_id = pci->bdf;
Kevin O'Connor051275b2013-10-26 11:53:38 -0400218 mlun->pci_id = pci->device;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100219 mlun->target = target;
220 mlun->lun = lun;
221 mlun->iobase = iobase;
222 mlun->frame = memalign_low(256, sizeof(struct megasas_cmd_frame));
223 if (!mlun->frame) {
224 warn_noalloc();
225 free(mlun);
226 return -1;
227 }
Kevin O'Connor937ca6f2016-02-03 03:27:36 -0500228 name = znprintf(MAXDESCSIZE, "MegaRAID SAS (PCI %pP) LD %d:%d"
229 , pci, target, lun);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100230 prio = bootprio_find_scsi_device(pci, target, lun);
Kevin O'Connord83c87b2013-01-21 01:14:12 -0500231 ret = scsi_drive_setup(&mlun->drive, name, prio);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100232 free(name);
233 if (ret) {
234 free(mlun->frame);
235 free(mlun);
236 ret = -1;
237 }
238
239 return ret;
240}
241
242static void megasas_scan_target(struct pci_device *pci, u32 iobase)
243{
244 struct mfi_ld_list_s ld_list;
245 struct megasas_cmd_frame *frame = memalign_tmp(256, sizeof(*frame));
Kevin O'Connor3abdc7c2015-06-30 11:10:41 -0400246 if (!frame) {
247 warn_noalloc();
248 return;
249 }
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100250
251 memset(&ld_list, 0, sizeof(ld_list));
252 memset_fl(frame, 0, sizeof(*frame));
253
254 frame->cmd = MFI_CMD_DCMD;
255 frame->cmd_status = 0xFF;
256 frame->sge_count = 1;
257 frame->flags = 0x0011;
258 frame->data_xfer_len = sizeof(ld_list);
259 frame->dcmd.opcode = 0x03010000;
260 frame->dcmd.sgl_addr = (u32)MAKE_FLATPTR(GET_SEG(SS), &ld_list);
261 frame->dcmd.sgl_len = sizeof(ld_list);
262 frame->context = (u32)frame;
263
264 if (megasas_fire_cmd(pci->device, iobase, frame) == 0) {
265 dprintf(2, "%d LD found\n", ld_list.count);
Kevin O'Connor3abdc7c2015-06-30 11:10:41 -0400266 int i;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100267 for (i = 0; i < ld_list.count; i++) {
268 dprintf(2, "LD %d:%d state 0x%x\n",
269 ld_list.lds[i].target, ld_list.lds[i].lun,
270 ld_list.lds[i].state);
271 if (ld_list.lds[i].state != 0) {
272 megasas_add_lun(pci, iobase,
273 ld_list.lds[i].target, ld_list.lds[i].lun);
274 }
275 }
276 }
277}
278
279static int megasas_transition_to_ready(struct pci_device *pci, u32 ioaddr)
280{
281 u32 fw_state = 0, new_state, mfi_flags = 0;
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100282
283 if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
284 pci->device == PCI_DEVICE_ID_DELL_PERC5)
285 new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK;
286 else
287 new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK;
288
289 while (fw_state != new_state) {
290 switch (new_state) {
291 case MFI_STATE_FAULT:
292 dprintf(1, "ERROR: fw in fault state\n");
293 return -1;
294 break;
295 case MFI_STATE_WAIT_HANDSHAKE:
296 mfi_flags = 0x08;
297 /* fallthrough */
298 case MFI_STATE_BOOT_MESSAGE_PENDING:
299 mfi_flags |= 0x10;
300 if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
301 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
302 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
303 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
Stefan Weila8c72ee2015-08-29 08:02:38 +0200304 outl(mfi_flags, ioaddr + MFI_DB);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100305 } else {
Stefan Weila8c72ee2015-08-29 08:02:38 +0200306 outl(mfi_flags, ioaddr + MFI_IDB);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100307 }
308 break;
309 case MFI_STATE_OPERATIONAL:
310 mfi_flags = 0x07;
311 if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
312 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
313 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
314 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
Stefan Weila8c72ee2015-08-29 08:02:38 +0200315 outl(mfi_flags, ioaddr + MFI_DB);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100316 if (pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
317 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
318 int j = 0;
319 u32 doorbell;
320
321 while (j < MEGASAS_POLL_TIMEOUT) {
322 doorbell = inl(ioaddr + MFI_DB) & 1;
323 if (!doorbell)
324 break;
325 msleep(20);
326 j++;
327 }
328 }
329 } else {
Kevin O'Connorff28e3b2015-09-03 10:00:57 -0400330 outl(mfi_flags, ioaddr + MFI_IDB);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100331 }
332 break;
333 case MFI_STATE_READY:
334 dprintf(2, "MegaRAID SAS fw ready\n");
335 return 0;
336 }
337 // The current state should not last longer than poll timeout
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400338 u32 end = timer_calc(MEGASAS_POLL_TIMEOUT);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100339 for (;;) {
Kevin O'Connor018bdd72013-07-20 18:22:57 -0400340 if (timer_check(end)) {
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100341 break;
342 }
343 yield();
344 fw_state = new_state;
345 if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
346 pci->device == PCI_DEVICE_ID_DELL_PERC5)
347 new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK;
348 else
349 new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK;
350 if (new_state != fw_state) {
351 break;
352 }
353 }
354 }
355 dprintf(1, "ERROR: fw in state %x\n", new_state & MFI_STATE_MASK);
356 return -1;
357}
358
359static void
Kevin O'Connor79bafa12016-04-05 13:04:07 -0400360init_megasas(void *data)
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100361{
Kevin O'Connor79bafa12016-04-05 13:04:07 -0400362 struct pci_device *pci = data;
Kevin O'Connor71f48442016-02-02 22:19:35 -0500363 u32 bar = PCI_BASE_ADDRESS_2;
364 if (!(pci_config_readl(pci->bdf, bar) & PCI_BASE_ADDRESS_IO_MASK))
365 bar = PCI_BASE_ADDRESS_0;
366 u32 iobase = pci_enable_iobar(pci, bar);
Hannes Reinecke09f876f2014-11-06 14:31:56 +0100367 if (!iobase)
Kevin O'Connor71f48442016-02-02 22:19:35 -0500368 return;
369 pci_enable_busmaster(pci);
Hannes Reinecke09f876f2014-11-06 14:31:56 +0100370
Kevin O'Connor7b673002016-02-03 03:03:15 -0500371 dprintf(1, "found MegaRAID SAS at %pP, io @ %x\n", pci, iobase);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100372
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100373 // reset
374 if (megasas_transition_to_ready(pci, iobase) == 0)
375 megasas_scan_target(pci, iobase);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100376}
377
378void
379megasas_setup(void)
380{
381 ASSERT32FLAT();
382 if (!CONFIG_MEGASAS)
383 return;
384
385 dprintf(3, "init megasas\n");
386
387 struct pci_device *pci;
388 foreachpci(pci) {
389 if (pci->vendor != PCI_VENDOR_ID_LSI_LOGIC &&
390 pci->vendor != PCI_VENDOR_ID_DELL)
391 continue;
Hannes Reinecke261e8702012-12-19 15:12:47 +0100392 if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
393 pci->device == PCI_DEVICE_ID_LSI_SAS1078 ||
394 pci->device == PCI_DEVICE_ID_LSI_SAS1078DE ||
395 pci->device == PCI_DEVICE_ID_LSI_SAS2108 ||
396 pci->device == PCI_DEVICE_ID_LSI_SAS2108E ||
397 pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
398 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
399 pci->device == PCI_DEVICE_ID_LSI_VERDE_ZCR ||
400 pci->device == PCI_DEVICE_ID_DELL_PERC5 ||
401 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
402 pci->device == PCI_DEVICE_ID_LSI_SAS3108)
Kevin O'Connor79bafa12016-04-05 13:04:07 -0400403 run_thread(init_megasas, pci);
Hannes Reinecke2df70bf2012-11-13 15:03:31 +0100404 }
405}