1 // 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 
13 #include "biosvar.h" // GET_GLOBALFLAT
14 #include "block.h" // struct drive_s
15 #include "blockcmd.h" // scsi_drive_setup
16 #include "config.h" // CONFIG_*
17 #include "malloc.h" // free
18 #include "output.h" // dprintf
19 #include "pci.h" // pci_config_readl
20 #include "pcidevice.h" // foreachpci
21 #include "pci_ids.h" // PCI_DEVICE_ID_XXX
22 #include "pci_regs.h" // PCI_VENDOR_ID
23 #include "stacks.h" // yield
24 #include "std/disk.h" // DISK_RET_SUCCESS
25 #include "string.h" // memset
26 #include "util.h" // timer_calc
27 
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 */
45 typedef 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 
57 struct 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 
96 struct 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 
111 struct megasas_lun_s {
112     struct drive_s drive;
113     struct megasas_cmd_frame *frame;
114     u32 iobase;
115     u16 pci_id;
116     u8 target;
117     u8 lun;
118 };
119 
megasas_fire_cmd(u16 pci_id,u32 ioaddr,struct megasas_cmd_frame * frame)120 static 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;
126 
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 
140     u32 end = timer_calc(MEGASAS_POLL_TIMEOUT);
141     do {
142         for (;;) {
143             cmd_state = GET_LOWFLAT(frame->cmd_status);
144             if (cmd_state != 0xff)
145                 break;
146             if (timer_check(end)) {
147                 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 
160 int
megasas_process_op(struct disk_op_s * op)161 megasas_process_op(struct disk_op_s *op)
162 {
163     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);
169     struct megasas_lun_s *mlun_gf =
170         container_of(op->drive_fl, struct megasas_lun_s, drive);
171     struct megasas_cmd_frame *frame = GET_GLOBALFLAT(mlun_gf->frame);
172     u16 pci_id = GET_GLOBALFLAT(mlun_gf->pci_id);
173     int i;
174 
175     memset_fl(frame, 0, sizeof(*frame));
176     SET_LOWFLAT(frame->cmd, MFI_CMD_LD_SCSI_IO);
177     SET_LOWFLAT(frame->cmd_status, 0xFF);
178     SET_LOWFLAT(frame->target_id, GET_GLOBALFLAT(mlun_gf->target));
179     SET_LOWFLAT(frame->lun, GET_GLOBALFLAT(mlun_gf->lun));
180     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 
197     if (megasas_fire_cmd(pci_id, GET_GLOBALFLAT(mlun_gf->iobase), frame) == 0)
198         return DISK_RET_SUCCESS;
199 
200     dprintf(2, "pthru cmd 0x%x failed\n", cdb[0]);
201     return DISK_RET_EBADTRACK;
202 }
203 
204 static int
megasas_add_lun(struct pci_device * pci,u32 iobase,u8 target,u8 lun)205 megasas_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;
218     mlun->pci_id = pci->device;
219     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     }
228     boot_lchs_find_scsi_device(pci, target, lun, &(mlun->drive.lchs));
229     name = znprintf(MAXDESCSIZE, "MegaRAID SAS (PCI %pP) LD %d:%d"
230                     , pci, target, lun);
231     prio = bootprio_find_scsi_device(pci, target, lun);
232     ret = scsi_drive_setup(&mlun->drive, name, prio);
233     free(name);
234     if (ret) {
235         free(mlun->frame);
236         free(mlun);
237         ret = -1;
238     }
239 
240     return ret;
241 }
242 
megasas_scan_target(struct pci_device * pci,u32 iobase)243 static void megasas_scan_target(struct pci_device *pci, u32 iobase)
244 {
245     struct mfi_ld_list_s ld_list;
246     struct megasas_cmd_frame *frame = memalign_tmp(256, sizeof(*frame));
247     if (!frame) {
248         warn_noalloc();
249         return;
250     }
251 
252     memset(&ld_list, 0, sizeof(ld_list));
253     memset_fl(frame, 0, sizeof(*frame));
254 
255     frame->cmd = MFI_CMD_DCMD;
256     frame->cmd_status = 0xFF;
257     frame->sge_count = 1;
258     frame->flags = 0x0011;
259     frame->data_xfer_len = sizeof(ld_list);
260     frame->dcmd.opcode = 0x03010000;
261     frame->dcmd.sgl_addr = (u32)MAKE_FLATPTR(GET_SEG(SS), &ld_list);
262     frame->dcmd.sgl_len = sizeof(ld_list);
263     frame->context = (u32)frame;
264 
265     if (megasas_fire_cmd(pci->device, iobase, frame) == 0) {
266         dprintf(2, "%d LD found\n", ld_list.count);
267         int i;
268         for (i = 0; i < ld_list.count; i++) {
269             dprintf(2, "LD %d:%d state 0x%x\n",
270                     ld_list.lds[i].target, ld_list.lds[i].lun,
271                     ld_list.lds[i].state);
272             if (ld_list.lds[i].state != 0) {
273                 megasas_add_lun(pci, iobase,
274                                 ld_list.lds[i].target, ld_list.lds[i].lun);
275             }
276         }
277     }
278 }
279 
megasas_transition_to_ready(struct pci_device * pci,u32 ioaddr)280 static int megasas_transition_to_ready(struct pci_device *pci, u32 ioaddr)
281 {
282     u32 fw_state = 0, new_state, mfi_flags = 0;
283 
284     if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
285         pci->device == PCI_DEVICE_ID_DELL_PERC5)
286         new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK;
287     else
288         new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK;
289 
290     while (fw_state != new_state) {
291         switch (new_state) {
292         case MFI_STATE_FAULT:
293             dprintf(1, "ERROR: fw in fault state\n");
294             return -1;
295             break;
296         case MFI_STATE_WAIT_HANDSHAKE:
297             mfi_flags = 0x08;
298             /* fallthrough */
299         case MFI_STATE_BOOT_MESSAGE_PENDING:
300             mfi_flags |= 0x10;
301             if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
302                 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
303                 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
304                 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
305                 outl(mfi_flags, ioaddr + MFI_DB);
306             } else {
307                 outl(mfi_flags, ioaddr + MFI_IDB);
308             }
309             break;
310         case MFI_STATE_OPERATIONAL:
311             mfi_flags = 0x07;
312             if (pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
313                 pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
314                 pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
315                 pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
316                 outl(mfi_flags, ioaddr + MFI_DB);
317                 if (pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
318                     pci->device == PCI_DEVICE_ID_LSI_SAS3108) {
319                     int j = 0;
320                     u32 doorbell;
321 
322                     while (j < MEGASAS_POLL_TIMEOUT) {
323                         doorbell = inl(ioaddr + MFI_DB) & 1;
324                         if (!doorbell)
325                             break;
326                         msleep(20);
327                         j++;
328                     }
329                 }
330             } else {
331                 outl(mfi_flags, ioaddr + MFI_IDB);
332             }
333             break;
334         case MFI_STATE_READY:
335             dprintf(2, "MegaRAID SAS fw ready\n");
336             return 0;
337         }
338         // The current state should not last longer than poll timeout
339         u32 end = timer_calc(MEGASAS_POLL_TIMEOUT);
340         for (;;) {
341             if (timer_check(end)) {
342                 break;
343             }
344             yield();
345             fw_state = new_state;
346             if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
347                 pci->device == PCI_DEVICE_ID_DELL_PERC5)
348                 new_state = inl(ioaddr + MFI_OMSG0) & MFI_STATE_MASK;
349             else
350                 new_state = inl(ioaddr + MFI_OSP0) & MFI_STATE_MASK;
351             if (new_state != fw_state) {
352                 break;
353             }
354         }
355     }
356     dprintf(1, "ERROR: fw in state %x\n", new_state & MFI_STATE_MASK);
357     return -1;
358 }
359 
360 static void
init_megasas(void * data)361 init_megasas(void *data)
362 {
363     struct pci_device *pci = data;
364     u32 bar = PCI_BASE_ADDRESS_2;
365     if (!(pci_config_readl(pci->bdf, bar) & PCI_BASE_ADDRESS_IO_MASK))
366         bar = PCI_BASE_ADDRESS_0;
367     u32 iobase = pci_enable_iobar(pci, bar);
368     if (!iobase)
369         return;
370     pci_enable_busmaster(pci);
371 
372     dprintf(1, "found MegaRAID SAS at %pP, io @ %x\n", pci, iobase);
373 
374     // reset
375     if (megasas_transition_to_ready(pci, iobase) == 0)
376         megasas_scan_target(pci, iobase);
377 }
378 
379 void
megasas_setup(void)380 megasas_setup(void)
381 {
382     ASSERT32FLAT();
383     if (!CONFIG_MEGASAS)
384         return;
385 
386     dprintf(3, "init megasas\n");
387 
388     struct pci_device *pci;
389     foreachpci(pci) {
390         if (pci->vendor != PCI_VENDOR_ID_LSI_LOGIC &&
391             pci->vendor != PCI_VENDOR_ID_DELL)
392             continue;
393         if (pci->device == PCI_DEVICE_ID_LSI_SAS1064R ||
394             pci->device == PCI_DEVICE_ID_LSI_SAS1078 ||
395             pci->device == PCI_DEVICE_ID_LSI_SAS1078DE ||
396             pci->device == PCI_DEVICE_ID_LSI_SAS2108 ||
397             pci->device == PCI_DEVICE_ID_LSI_SAS2108E ||
398             pci->device == PCI_DEVICE_ID_LSI_SAS2004 ||
399             pci->device == PCI_DEVICE_ID_LSI_SAS2008 ||
400             pci->device == PCI_DEVICE_ID_LSI_VERDE_ZCR ||
401             pci->device == PCI_DEVICE_ID_DELL_PERC5 ||
402             pci->device == PCI_DEVICE_ID_LSI_SAS2208 ||
403             pci->device == PCI_DEVICE_ID_LSI_SAS3108)
404             run_thread(init_megasas, pci);
405     }
406 }
407