SVN-fs-dump-format-version: 2 UUID: 420bcfab-8a21-0410-a962-c4559dea6134 Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2006-11-06T02:16:18.519022Z PROPS-END Revision-number: 1 Prop-content-length: 137 Content-length: 137 K 7 svn:log V 34 empty file, lets get this started K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-07T01:54:34.024098Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 0 Text-content-md5: d41d8cd98f00b204e9800998ecf8427e Content-length: 10 PROPS-END Revision-number: 2 Prop-content-length: 137 Content-length: 137 K 7 svn:log V 34 a basic makefile to get me going K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-07T02:52:41.094239Z PROPS-END Node-path: Makefile Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 122 Text-content-md5: ddda7aba78161d62d061a4d8f96e33e9 Content-length: 132 PROPS-END all: mfi mfi.o: mfi.c cc -D_KERNEL -xarch=amd64 -xmodel=kernel -c mfi.c mfi: mfi.o /usr/ccs/bin/ld -r -o mfi mfi.o Revision-number: 3 Prop-content-length: 120 Content-length: 120 K 7 svn:log V 17 add placeholders K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-07T06:02:28.085807Z PROPS-END Node-path: mfi.conf Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 0 Text-content-md5: d41d8cd98f00b204e9800998ecf8427e Content-length: 10 PROPS-END Node-path: mfireg.h Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 792 Text-content-md5: 02df73dcc3aa37d99cfb1b6b1d8799c3 Content-length: 802 PROPS-END /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ Revision-number: 4 Prop-content-length: 175 Content-length: 175 K 7 svn:log V 72 add some useful targets. this is way messier than it really should be. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-07T06:02:49.858506Z PROPS-END Node-path: Makefile Node-kind: file Node-action: change Text-content-length: 307 Text-content-md5: 66d2d96448e7860dd82726dbdf50169f Content-length: 307 all: mfi clean: rm mfi.o mfi install: mfi sudo cp mfi /tmp load: sudo add_drv -i pci1028,15 mfi unload: sudo rem_drv mfi reload: sudo rem_drv mfi sleep 1 sudo add_drv -i pci1028,15 mfi mfi: mfi.o /usr/ccs/bin/ld -r -o mfi mfi.o mfi.o: mfi.c cc -D_KERNEL -xarch=amd64 -xmodel=kernel -c mfi.c Revision-number: 5 Prop-content-length: 162 Content-length: 162 K 7 svn:log V 59 working initial framework of a driver happy happy happy! K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-07T06:03:35.732737Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 3019 Text-content-md5: f28447ea2aea18bdfdac1f9193584f2a Content-length: 3019 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "MFI Driver", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; }; struct mfi_softc *sc; static void *mfi_softc_p; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 0); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "inside _info"); return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); return (DDI_FAILURE); } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; return (DDI_SUCCESS); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } Revision-number: 6 Prop-content-length: 126 Content-length: 126 K 7 svn:log V 23 remove useless logging K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-07T06:13:25.164732Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 2984 Text-content-md5: 42f5bcd85bf7964029d25dcef685dd70 Content-length: 2984 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "MFI Driver", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; }; struct mfi_softc *sc; static void *mfi_softc_p; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 0); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); return (DDI_FAILURE); } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; return (DDI_SUCCESS); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } Revision-number: 7 Prop-content-length: 136 Content-length: 136 K 7 svn:log V 33 mfi.o relies on mfireg.h as well K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T01:36:04.675231Z PROPS-END Node-path: Makefile Node-kind: file Node-action: change Text-content-length: 316 Text-content-md5: 67a083d74a1e056346e48ce66e2045c8 Content-length: 316 all: mfi clean: rm mfi.o mfi install: mfi sudo cp mfi /tmp load: sudo add_drv -i pci1028,15 mfi unload: sudo rem_drv mfi reload: sudo rem_drv mfi sleep 1 sudo add_drv -i pci1028,15 mfi mfi: mfi.o /usr/ccs/bin/ld -r -o mfi mfi.o mfi.o: mfi.c mfireg.h cc -D_KERNEL -xarch=amd64 -xmodel=kernel -c mfi.c Revision-number: 8 Prop-content-length: 147 Content-length: 147 K 7 svn:log V 44 lets start by listing the register offsets. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T01:36:58.741484Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 1402 Text-content-md5: 939f3ebb74df64076465cd667acf6036 Content-length: 1402 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ Revision-number: 9 Prop-content-length: 180 Content-length: 180 K 7 svn:log V 77 do pci config space magic to force bus mastering on. map the register space. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T01:37:44.884038Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 4443 Text-content-md5: caa252b00a7ee91c2d0f3661a768a42e Content-length: 4443 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; }; static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; caddr_t cfgaddr; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; /* fix things up in pci space */ if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, &cfgaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } return (DDI_SUCCESS); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } /* we could check subproduct and subvendor for names */ pci_config_teardown(&pci_conf); return (rv); } Revision-number: 10 Prop-content-length: 205 Content-length: 205 K 7 svn:log V 101 glue for access to the register space. ugh, this is disgusting. i miss bus_space so much already :( K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T02:03:50.957319Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 4696 Text-content-md5: 0bdece48d28f2d5f132205a6a1c01e30 Content-length: 4696 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; }; #define mfi_read(_s, _r) ddi_get_32((_s)->sc_reg_space, \ (u_int32_t *)((_s)->sc_reg_busaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_get_32((_s)->sc_reg_space, \ (u_int32_t *)((_s)->sc_reg_busaddr + (_r)), (_v)) static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; /* fix things up in pci space */ if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } return (DDI_SUCCESS); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } /* we could check subproduct and subvendor for names */ pci_config_teardown(&pci_conf); return (rv); } Revision-number: 11 Prop-content-length: 132 Content-length: 132 K 7 svn:log V 29 oops, put for write, not get K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T02:04:30.945709Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 4696 Text-content-md5: 34146c9555996d7d6f4008f293c9fbab Content-length: 4696 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; }; #define mfi_read(_s, _r) ddi_get_32((_s)->sc_reg_space, \ (u_int32_t *)((_s)->sc_reg_busaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put_32((_s)->sc_reg_space, \ (u_int32_t *)((_s)->sc_reg_busaddr + (_r)), (_v)) static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; /* fix things up in pci space */ if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } return (DDI_SUCCESS); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } /* we could check subproduct and subvendor for names */ pci_config_teardown(&pci_conf); return (rv); } Revision-number: 12 Prop-content-length: 118 Content-length: 118 K 7 svn:log V 15 more registers K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T04:16:03.494013Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 2247 Text-content-md5: cb3657c2ce5f1a5690abc84729687bc8 Content-length: 2247 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) Revision-number: 13 Prop-content-length: 276 Content-length: 276 K 7 svn:log V 172 fix the bus space access macros. add code to busy wait on values in them. transition the firmware if it isnt already ready. i dont know if i got the delay right though :/ K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T04:17:43.565986Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 6734 Text-content-md5: 2ff9cc650973c0f7f14263f4cfea068e Content-length: 6734 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t reg; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; /* fix things up in pci space */ if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ cmn_err(CE_NOTE, "omghi"); if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto free_sc; } cmn_err(CE_NOTE, "reg: 0x%08x", mfi_read(sc, MFI_OMSG0)); return (DDI_SUCCESS); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 14 Prop-content-length: 133 Content-length: 133 K 7 svn:log V 30 inital hookups for interrupts K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T04:53:54.757440Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 6955 Text-content-md5: a2987f6a8a16d6eb723e91a400ad312a Content-length: 6955 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t reg; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; /* fix things up in pci space */ if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto free_sc; } return (DDI_SUCCESS); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 15 Prop-content-length: 219 Content-length: 219 K 7 svn:log V 115 create two mutexes: one each for the reply and post queues. also create a taskq to push replies onto for handling. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-08T06:30:06.799037Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 7606 Text-content-md5: ce49e1d4964f78990855362eb48aad90 Content-length: 7606 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t reg; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 1 /* XXX */, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } return (DDI_SUCCESS); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 16 Prop-content-length: 138 Content-length: 138 K 7 svn:log V 35 remember max commands and sgl len K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T00:17:54.680560Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 7756 Text-content-md5: 90c0f405cd04b6ff195066c15d5b6e62 Content-length: 7756 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; int sc_ncmds; int sc_sgllen; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_reg_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_reg_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); return (DDI_SUCCESS); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 2355 Text-content-md5: 7f49b7363b54a3537a9da89a1876975e Content-length: 2355 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) Revision-number: 17 Prop-content-length: 195 Content-length: 195 K 7 svn:log V 92 describe the mfi command header/template, the firmware init command, and the qinfo message. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T06:09:51.125424Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 3357 Text-content-md5: 57281aee2fe884f7a45e2ea05ca0327e Content-length: 3357 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #pragma pack(1) struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t cmd_status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context_lo; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context_lo; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 18 Prop-content-length: 162 Content-length: 162 K 7 svn:log V 59 allocate memory for the producer, consumer, and the replyq K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T06:10:09.160760Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 10698 Text-content-md5: a39b127f9219fda472be31fcde139c96 Content-length: 10698 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc { dev_info_t *sc_dev; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; int sc_ncmds; int sc_sgllen; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static void *mfi_softc_p; static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); return (DDI_SUCCESS); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 19 Prop-content-length: 113 Content-length: 113 K 7 svn:log V 10 cmd flags K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T06:15:14.388614Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 3545 Text-content-md5: 72682cb4a67212b9d0fb2aaa2f8252b9 Content-length: 3545 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #pragma pack(1) struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t cmd_status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context_lo; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context_lo; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 20 Prop-content-length: 176 Content-length: 176 K 7 svn:log V 73 pull in openbsd queue.h since i am too lazy to find the equiv in solaris K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T06:37:38.223761Z PROPS-END Node-path: queue.h Node-kind: file Node-action: add Prop-content-length: 10 Text-content-length: 18064 Text-content-md5: 7a38192156b76c7e24320f9612ac1605 Content-length: 18074 PROPS-END /* $OpenBSD: queue.h,v 1.31 2005/11/25 08:06:25 otto Exp $ */ /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues, and circular queues. * * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. */ #ifdef QUEUE_MACRO_DEBUG #define _Q_INVALIDATE(a) (a) = ((void *)-1) #else #define _Q_INVALIDATE(a) #endif /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List access methods. */ #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_END(head) NULL #define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_FOREACH(var, head, field) \ for((var) = SLIST_FIRST(head); \ (var) != SLIST_END(head); \ (var) = SLIST_NEXT(var, field)) #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ for ((varp) = &SLIST_FIRST((head)); \ ((var) = *(varp)) != SLIST_END(head); \ (varp) = &SLIST_NEXT((var), field)) /* * Singly-linked List functions. */ #define SLIST_INIT(head) { \ SLIST_FIRST(head) = SLIST_END(head); \ } #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (0) #define SLIST_REMOVE_NEXT(head, elm, field) do { \ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (0) #define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->slh_first; \ \ while (curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ _Q_INVALIDATE((elm)->field.sle_next); \ } \ } while (0) /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List access methods */ #define LIST_FIRST(head) ((head)->lh_first) #define LIST_END(head) NULL #define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_FOREACH(var, head, field) \ for((var) = LIST_FIRST(head); \ (var)!= LIST_END(head); \ (var) = LIST_NEXT(var, field)) /* * List functions. */ #define LIST_INIT(head) do { \ LIST_FIRST(head) = LIST_END(head); \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (0) #define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) #define LIST_REPLACE(elm, elm2, field) do { \ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ (elm2)->field.le_next->field.le_prev = \ &(elm2)->field.le_next; \ (elm2)->field.le_prev = (elm)->field.le_prev; \ *(elm2)->field.le_prev = (elm2); \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) /* * Simple queue definitions. */ #define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ { NULL, &(head).sqh_first } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ } /* * Simple queue access methods. */ #define SIMPLEQ_FIRST(head) ((head)->sqh_first) #define SIMPLEQ_END(head) NULL #define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) #define SIMPLEQ_FOREACH(var, head, field) \ for((var) = SIMPLEQ_FIRST(head); \ (var) != SIMPLEQ_END(head); \ (var) = SIMPLEQ_NEXT(var, field)) /* * Simple queue functions. */ #define SIMPLEQ_INIT(head) do { \ (head)->sqh_first = NULL; \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_first = (elm); \ } while (0) #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.sqe_next = NULL; \ *(head)->sqh_last = (elm); \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (0) #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ (head)->sqh_last = &(elm)->field.sqe_next; \ (listelm)->field.sqe_next = (elm); \ } while (0) #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) /* * Tail queue definitions. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ } /* * tail queue access methods */ #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_END(head) NULL #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) /* XXX */ #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_EMPTY(head) \ (TAILQ_FIRST(head) == TAILQ_END(head)) #define TAILQ_FOREACH(var, head, field) \ for((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head); \ (var) = TAILQ_NEXT(var, field)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head); \ (var) = TAILQ_PREV(var, headname, field)) /* * Tail queue functions. */ #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) #define TAILQ_REPLACE(head, elm, elm2, field) do { \ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ (elm2)->field.tqe_next->field.tqe_prev = \ &(elm2)->field.tqe_next; \ else \ (head)->tqh_last = &(elm2)->field.tqe_next; \ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ *(elm2)->field.tqe_prev = (elm2); \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) /* * Circular queue definitions. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue access methods */ #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_END(head) ((void *)(head)) #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) #define CIRCLEQ_EMPTY(head) \ (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) #define CIRCLEQ_FOREACH(var, head, field) \ for((var) = CIRCLEQ_FIRST(head); \ (var) != CIRCLEQ_END(head); \ (var) = CIRCLEQ_NEXT(var, field)) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for((var) = CIRCLEQ_LAST(head); \ (var) != CIRCLEQ_END(head); \ (var) = CIRCLEQ_PREV(var, field)) /* * Circular queue functions. */ #define CIRCLEQ_INIT(head) do { \ (head)->cqh_first = CIRCLEQ_END(head); \ (head)->cqh_last = CIRCLEQ_END(head); \ } while (0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm)->field.cqe_next; \ (elm)->field.cqe_prev = (listelm); \ if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ (head)->cqh_last = (elm); \ else \ (listelm)->field.cqe_next->field.cqe_prev = (elm); \ (listelm)->field.cqe_next = (elm); \ } while (0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm); \ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ (head)->cqh_first = (elm); \ else \ (listelm)->field.cqe_prev->field.cqe_next = (elm); \ (listelm)->field.cqe_prev = (elm); \ } while (0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ (elm)->field.cqe_next = (head)->cqh_first; \ (elm)->field.cqe_prev = CIRCLEQ_END(head); \ if ((head)->cqh_last == CIRCLEQ_END(head)) \ (head)->cqh_last = (elm); \ else \ (head)->cqh_first->field.cqe_prev = (elm); \ (head)->cqh_first = (elm); \ } while (0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.cqe_next = CIRCLEQ_END(head); \ (elm)->field.cqe_prev = (head)->cqh_last; \ if ((head)->cqh_first == CIRCLEQ_END(head)) \ (head)->cqh_first = (elm); \ else \ (head)->cqh_last->field.cqe_next = (elm); \ (head)->cqh_last = (elm); \ } while (0) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ (head)->cqh_last = (elm)->field.cqe_prev; \ else \ (elm)->field.cqe_next->field.cqe_prev = \ (elm)->field.cqe_prev; \ if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ (head)->cqh_first = (elm)->field.cqe_next; \ else \ (elm)->field.cqe_prev->field.cqe_next = \ (elm)->field.cqe_next; \ _Q_INVALIDATE((elm)->field.cqe_prev); \ _Q_INVALIDATE((elm)->field.cqe_next); \ } while (0) #define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ CIRCLEQ_END(head)) \ (head).cqh_last = (elm2); \ else \ (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ CIRCLEQ_END(head)) \ (head).cqh_first = (elm2); \ else \ (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ _Q_INVALIDATE((elm)->field.cqe_prev); \ _Q_INVALIDATE((elm)->field.cqe_next); \ } while (0) #endif /* !_SYS_QUEUE_H_ */ Revision-number: 21 Prop-content-length: 176 Content-length: 176 K 7 svn:log V 73 we only use 32 bits of context, tweak the name to make this more obvious K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T06:39:51.347861Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 3542 Text-content-md5: 7c67f13bbd6e8d0a529c740aef0d707f Content-length: 3542 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #pragma pack(1) struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t cmd_status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context_lo; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 22 Prop-content-length: 258 Content-length: 258 K 7 svn:log V 154 first cut at allocating a ccb list to keep track of context ids with. something isnt right, but im committing this to show it to someone smarter than me. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T12:42:52.112583Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 12384 Text-content-md5: 9aaffd61e76710239723750e1090fe4d Content-length: 12384 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); return (DDI_SUCCESS); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 23 Prop-content-length: 212 Content-length: 212 K 7 svn:log V 108 read the number of commands off before allocating the replyq and ccb list. im an idiot, pointed out by jmcp K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-09T13:13:28.237288Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 12384 Text-content-md5: 1c14835f5acad48ec917cd955edf4bd9 Content-length: 12384 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "mfireg.h" #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } return (DDI_SUCCESS); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 24 Prop-content-length: 159 Content-length: 159 K 7 svn:log V 56 turn on warnings. my code was clean though, so im happy K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T04:47:36.590981Z PROPS-END Node-path: Makefile Node-kind: file Node-action: change Text-content-length: 602 Text-content-md5: 6d01535ae679a7459813683446f5b7ae Content-length: 602 all: mfi clean: rm mfi.o mfi install: mfi sudo cp mfi /tmp load: sudo add_drv -i pci1028,15 mfi unload: sudo rem_drv mfi reload: sudo rem_drv mfi sleep 1 sudo add_drv -i pci1028,15 mfi mfi: mfi.o /usr/ccs/bin/ld -r -o mfi mfi.o mfi.o: mfi.c mfireg.h cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -c mfi.c #cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -Wd,-xsafe=unboundsym -c mfi.c Revision-number: 25 Prop-content-length: 165 Content-length: 165 K 7 svn:log V 62 frames are 64 bytes. add the four messages types i care about K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T04:50:58.314968Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 3729 Text-content-md5: 3c7bb5fb11bb5b75eaea451cf7c5bf66 Content-length: 3729 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #pragma pack(1) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t cmd_status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context_lo; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 26 Prop-content-length: 206 Content-length: 206 K 7 svn:log V 102 allocate memory for the ccbs command buffer, and send the fwinit command. seems to complete ok :D :D K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T04:51:53.339476Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 16308 Text-content-md5: 3ca56f9c98d1946ce58449206a3ed92f Content-length: 16308 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "mfireg.h" #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->status = 0xff; init->context = LE_32(ccb->ccb_context); init->flags = MFI_CMD_FLAG_NO_POST_IN_REPLYQ; dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); while (init->status == 0xff) { delay(drv_usectohz(10000)); } cmn_err(CE_NOTE, "omg initted"); return (DDI_SUCCESS); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 27 Prop-content-length: 215 Content-length: 215 K 7 svn:log V 111 fix the name of the status field in mfi_cmd_hdr generalise the poll for a command so it can be used elsewhere K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T06:14:38.431457Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 16970 Text-content-md5: 45bc20f9aa9bfb16028f43165c3a82a1 Content-length: 16970 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "mfireg.h" #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } delay(drv_usectohz(1000)); } /* cmd dma post rw sync */ return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 3726 Text-content-md5: 320f2f628c12cd69f3b98b86373e5d02 Content-length: 3726 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #pragma pack(1) struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context_lo; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 28 Prop-content-length: 139 Content-length: 139 K 7 svn:log V 36 more hints for proper syncing later K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T06:15:23.881739Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 16997 Text-content-md5: a8946c860d336a876f12e8f74e495962 Content-length: 16997 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "mfireg.h" #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 29 Prop-content-length: 117 Content-length: 117 K 7 svn:log V 14 more comments K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T06:16:42.649951Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 17065 Text-content-md5: 0ee6bdf9ad1e1b2e01afa411f56065be Content-length: 17065 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) { cmn_err(CE_NOTE, "unable to init soft state"); goto err; } error = mod_install(&ml); if (error != 0) { cmn_err(CE_NOTE, "mod_install error"); goto state_fini; } return (error); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 30 Prop-content-length: 137 Content-length: 137 K 7 svn:log V 34 scsi_hba bits for _init and _fini K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T06:24:23.617918Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 17082 Text-content-md5: d372fabc487f209647222c5824dc54cf Content-length: 17082 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 31 Prop-content-length: 155 Content-length: 155 K 7 svn:log V 52 add the hooks for the scsi midlayer to call into us K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-10T07:37:52.605243Z PROPS-END Node-path: Makefile Node-kind: file Node-action: change Text-content-length: 618 Text-content-md5: dd191bd7876d79fd4dd2e92bf2edc3ce Content-length: 618 all: mfi clean: rm mfi.o mfi install: mfi sudo cp mfi /tmp load: sudo add_drv -i pci1028,15 -c scsi mfi unload: sudo rem_drv mfi reload: sudo rem_drv mfi sleep 1 sudo add_drv -i pci1028,15 -c scsi mfi mfi: mfi.o /usr/ccs/bin/ld -r -o mfi mfi.o mfi.o: mfi.c mfireg.h cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -c mfi.c #cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -Wd,-xsafe=unboundsym -c mfi.c Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 20703 Text-content-md5: 578da08a7145e0df9999143af8fff741 Content-length: 20703 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { return (DDI_FAILURE); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; return (TRAN_BADPKT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { cmn_err(CE_NOTE, "mfi_tran_reset"); return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { cmn_err(CE_NOTE, "mfi_tran_getcap"); return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { cmn_err(CE_NOTE, "mfi_tran_setcap"); return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(), caddr_t arg) { cmn_err(CE_NOTE, "mfi_tran_init_pkt"); return (NULL); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { cmn_err(CE_NOTE, "mfi_tran_dmafree"); } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 32 Prop-content-length: 151 Content-length: 151 K 7 svn:log V 48 add the scsi command (with crap in it for now). K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-13T06:23:58.325047Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 4170 Text-content-md5: b1911ac034302508e49cbcb7b45c757f Content-length: 4170 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #pragma pack(1) struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; /* 4 */ uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; /* 8 */ uint32_t context; uint32_t context_hi; /* 16 */ uint16_t flags; uint16_t timeout; /* 20 */ uint32_t datalen; /* 24 */ uint32_t sense_lo; uint32_t sense_hi; /* 32 */ uint8_t cdb[16]; /* 44 */ struct { uint32_t addr; uint32_t len; } sgl[4]; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 33 Prop-content-length: 282 Content-length: 282 K 7 svn:log V 178 big chunk of code as a start to actually doing scsi io. it polls for completion at the moment, and im surprised that any scsi commands work at all. but hey, it's getting there. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-13T06:46:30.194265Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 25080 Text-content-md5: e7fa9a540521afcb2f0ff09db4a9ad33 Content-length: 25080 /* * Copyright (c) 2006 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies[4]; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_len; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 4, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &sc->sc_io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target > 255) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = ccb->ccb_cmd; memset(io, 0, 512); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->datalen = mpd->mpd_len; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { io->sgl[i].len = mpd->mpd_cookies[i].dmac_size; io->sgl[i].addr = mpd->mpd_cookies[i].dmac_address; } mfi_poll(sc, ccb, 1000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = CMD_CMPLT; pkt->pkt_resid = 0; pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; #if 0 cmn_err(CE_NOTE, "mfi_tran_init_pkt"); #endif /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > 16 /* XXX magic */) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; if (ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies[0], &mpd->mpd_ncookies) != DDI_DMA_MAPPED) { if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; mpd->mpd_len = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 34 Prop-content-length: 130 Content-length: 130 K 7 svn:log V 27 fix the copyright. so sad. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-13T10:49:27.253284Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 25258 Text-content-md5: d3fe008245f02298083cf00787b27df2 Content-length: 25258 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies[4]; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_len; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 4, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &sc->sc_io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target > 255) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = ccb->ccb_cmd; memset(io, 0, 512); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->datalen = mpd->mpd_len; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { io->sgl[i].len = mpd->mpd_cookies[i].dmac_size; io->sgl[i].addr = mpd->mpd_cookies[i].dmac_address; } mfi_poll(sc, ccb, 1000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = CMD_CMPLT; pkt->pkt_resid = 0; pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; #if 0 cmn_err(CE_NOTE, "mfi_tran_init_pkt"); #endif /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > 16 /* XXX magic */) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; if (ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies[0], &mpd->mpd_ncookies) != DDI_DMA_MAPPED) { if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; mpd->mpd_len = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 4348 Text-content-md5: f9bab47b7147346e153fb6a6ba98dc38 Content-length: 4348 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #pragma pack(1) struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; /* 4 */ uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; /* 8 */ uint32_t context; uint32_t context_hi; /* 16 */ uint16_t flags; uint16_t timeout; /* 20 */ uint32_t datalen; /* 24 */ uint32_t sense_lo; uint32_t sense_hi; /* 32 */ uint8_t cdb[16]; /* 44 */ struct { uint32_t addr; uint32_t len; } sgl[4]; }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 35 Prop-content-length: 114 Content-length: 114 K 7 svn:log V 11 i like c99 K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-14T02:14:11.571567Z PROPS-END Node-path: Makefile Node-kind: file Node-action: change Text-content-length: 765 Text-content-md5: 5ec24cbe5f8ae822763e1f20dc6f996d Content-length: 765 all: mfi clean: rm mfi.o mfi install: mfi sudo cp mfi /tmp load: sudo add_drv -i pci1028,15 -c scsi mfi unload: sudo rem_drv mfi reload: sudo rem_drv mfi sleep 1 sudo add_drv -i pci1028,15 -c scsi mfi mfi: mfi.o /usr/ccs/bin/ld -r mfi.o -o mfi mfi.o: mfi.c mfireg.h cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -c mfi.c # cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -c mfi.c #cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -Wd,-xsafe=unboundsym -c mfi.c Revision-number: 36 Prop-content-length: 171 Content-length: 171 K 7 svn:log V 68 c99 lets use use varargs in macros, and it finds missing prototypes K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-14T02:14:34.038499Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 25709 Text-content-md5: d04f31a0a0899e3e12af6975b2f21aee Content-length: 25709 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_CCB; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies[4]; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_len; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 4, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &sc->sc_io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target > 255) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = ccb->ccb_cmd; memset(io, 0, 512); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->datalen = mpd->mpd_len; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { io->sgl[i].len = mpd->mpd_cookies[i].dmac_size; io->sgl[i].addr = mpd->mpd_cookies[i].dmac_address; } mfi_poll(sc, ccb, 1000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = CMD_CMPLT; pkt->pkt_resid = 0; pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; #if 0 cmn_err(CE_NOTE, "mfi_tran_init_pkt"); #endif /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > 16 /* XXX magic */) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; if (ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies[0], &mpd->mpd_ncookies) != DDI_DMA_MAPPED) { if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; mpd->mpd_len = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, ccb->ccb_cmd_dva >> 3); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 37 Prop-content-length: 397 Content-length: 397 K 7 svn:log V 293 OMG I GOT IT WORKING! :D :D dlg@vavaea ~$ sudo format Searching for disks...done AVAILABLE DISK SELECTIONS: 1. c3t0d0 /pci@0,0/pci8086,3599@6/pci8086,370@0/pci1028,1f01@e/sd@0,0 format> inquiry Vendor: DELL Product: PERC 5/E Adapter Revision: 1.00 K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-15T07:06:09.454013Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27434 Text-content-md5: 42c2bddcd111695ea94b44439489b9b8 Content-length: 27434 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_len; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = ccb->ccb_cmd; memset(io, 0, 512); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->datalen = mpd->mpd_len; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { io->sgl[i].len = mpd->mpd_cookies.dmac_size; io->sgl[i].addr = mpd->mpd_cookies.dmac_address; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " datalen: %d (%d)", datalen, i); } /* io->datalen = datalen; */ /* DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); */ mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > 16 /* XXX magic */) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; mpd->mpd_len = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { uint32_t addr; addr = ccb->ccb_cmd_dva; cmn_err(CE_NOTE, "mfi_post: dva1: 0x%08x", addr); addr = ccb->ccb_cmd_dva >> 3; cmn_err(CE_NOTE, "mfi_post: dva2: 0x%08x", addr); addr = (ccb->ccb_cmd_dva >> 3) | 1; cmn_err(CE_NOTE, "mfi_post: dva3: 0x%08x", addr); /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, addr); /* (ccb->ccb_cmd_dva >> 3) | 1); */ } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 38 Prop-content-length: 142 Content-length: 142 K 7 svn:log V 39 remove the debug from the post command K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T00:55:49.376704Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27155 Text-content-md5: 407bc28c05dccdccb4be1692a12cea78 Content-length: 27155 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; void *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_len; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; uint8_t *kva; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); kva = ccb->ccb_cmd; bzero(kva, MFI_CCB_CMDLEN); init = ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(kva + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = ccb->ccb_cmd; memset(io, 0, 512); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->datalen = mpd->mpd_len; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { io->sgl[i].len = mpd->mpd_cookies.dmac_size; io->sgl[i].addr = mpd->mpd_cookies.dmac_address; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " datalen: %d (%d)", datalen, i); } /* io->datalen = datalen; */ /* DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); */ mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > 16 /* XXX magic */) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; mpd->mpd_len = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd = ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 39 Prop-content-length: 344 Content-length: 344 K 7 svn:log V 240 add definitions for the sg bits and split them out of the scsi cmd. make the pointer to the ccbs command buffer a uint8_t so i can do pointer math on it safely. this means more typecasts, but i had to do them anyway with a local variable. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T01:18:44.086760Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27118 Text-content-md5: f5311e806411ab31ed61b5511b2f099a Content-length: 27118 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; #define MFI_CCB_CMDLEN 512 TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_len; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; memset(io, 0, 512); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); datalen += mpd->mpd_cookies.dmac_size; } io->datalen = datalen; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > 16 /* XXX magic */) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; mpd->mpd_len = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_CMDLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 4403 Text-content-md5: 1e7d2bc1bb3b05dab14b7a4e037dd723 Content-length: 4403 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; uint8_t cdb[16]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 40 Prop-content-length: 347 Content-length: 347 K 7 svn:log V 243 allocate some extra space with the cdbs command buffer, mostly so we have somewhere to stash the sense data for scsi commands. now that we use the dma segment sizes to calculate the size of the transfer, we dont have to keep that in the mpd. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T01:49:20.626309Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27419 Text-content-md5: c9d6bd787bc72b238654090759045a16 Content-length: 27419 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; memset(io, 0, 512); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); datalen += mpd->mpd_cookies.dmac_size; } io->datalen = datalen; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 4451 Text-content-md5: 6e8c5981e863a299cf0b1e0c7fb70b48 Content-length: 4451 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; #define MFI_CMD_SCSI_CDBLEN 16 uint8_t cdb[MFI_CMD_SCSI_CDBLEN]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 41 Prop-content-length: 154 Content-length: 154 K 7 svn:log V 51 bzero the ccbs command buffer when we allocate it. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T01:51:16.499077Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27435 Text-content-md5: 467ec1fa5d56453602cbce57ee12e46f Content-length: 27435 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); datalen += mpd->mpd_cookies.dmac_size; } io->datalen = datalen; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mfi_free_cmdbuf(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 42 Prop-content-length: 165 Content-length: 165 K 7 svn:log V 62 keep track of how many extra command frames we use in the ccb K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T01:55:43.928389Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27489 Text-content-md5: e2f5f06771a847943c1f992e49415db1 Content-length: 27489 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); datalen += mpd->mpd_cookies.dmac_size; } io->datalen = datalen; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 43 Prop-content-length: 269 Content-length: 269 K 7 svn:log V 165 calculate the datalength properly by summing it with the current segment rather than the next one. we'd always miss the first segment and buffer lengths were wrong. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T02:18:24.940203Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27489 Text-content-md5: e3fce03add594317618150404a3f11b9 Content-length: 27489 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "starting 0x%08x\n", ccb->ccb_context); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); #ifdef MFI_DEBUG for (i = 0; i < sizeof(io->cdb); i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x\n", i, io->cdb[i]); } #endif return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 44 Prop-content-length: 168 Content-length: 168 K 7 svn:log V 65 print the scsi command debug output before we send it to the hw. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T02:21:19.721542Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27253 Text-content-md5: 29da203fcfc59a3301955a0153ac3230 Content-length: 27253 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); #if 0 cmn_err(CE_NOTE, "mfi_tran_start"); #endif io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); mfi_poll(sc, ccb, 10000); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 45 Prop-content-length: 125 Content-length: 125 K 7 svn:log V 22 strip some more debug K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T02:25:14.255175Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27203 Text-content-md5: c9c2c014d6a73f2ec86620c7a282d415 Content-length: 27203 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = 0; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x: cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); mfi_poll(sc, ccb, 10000); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA); pkt->pkt_reason = io->scsi_status; pkt->pkt_resid = datalen - io->datalen; pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 46 Prop-content-length: 125 Content-length: 125 K 7 svn:log V 22 command status values K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T07:21:59.271684Z PROPS-END Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 6739 Text-content-md5: 84277a7b7a3d5ee0ec67ee521e0b4aae Content-length: 6739 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #define MFI_STAT_OK 0x00 #define MFI_STAT_INVALID_CMD 0x01 #define MFI_STAT_INVALID_DCMD 0x02 #define MFI_STAT_INVALID_PARAMETER 0x03 #define MFI_STAT_INVALID_SEQ_NUMBER 0x04 #define MFI_STAT_ABORT_NOT_POSSIBLE 0x05 #define MFI_STAT_APP_HOST_CODE_NOT_FND 0x06 #define MFI_STAT_APP_IN_USE 0x07 #define MFI_STAT_APP_NOT_INITIALIZED 0x08 #define MFI_STAT_ARRAY_INDEX_INVALID 0x09 #define MFI_STAT_ARRAY_ROW_NOT_EMPTY 0x0a #define MFI_STAT_CFG_RESOURCE_CONFLICT 0x0b #define MFI_STAT_DEVICE_NOT_FOUND 0x0c #define MFI_STAT_DRIVE_TOO_SMALL 0x0d #define MFI_STAT_FLASH_ALLOC_FAIL 0x0e #define MFI_STAT_FLASH_BUSY 0x0f #define MFI_STAT_FLASH_ERROR 0x10 #define MFI_STAT_FLASH_IMAGE_BAD 0x11 #define MFI_STAT_FLASH_IMAGE_INCOMPLETE 0x12 #define MFI_STAT_FLASH_NOT_OPEN 0x13 #define MFI_STAT_FLASH_NOT_STARTED 0x14 #define MFI_STAT_FLUSH_FAILED 0x15 #define MFI_STAT_HOST_CODE_NOT_FOUNT 0x16 #define MFI_STAT_LD_CC_IN_PROGRESS 0x17 #define MFI_STAT_LD_INIT_IN_PROGRESS 0x18 #define MFI_STAT_LD_LBA_OUT_OF_RANGE 0x19 #define MFI_STAT_LD_MAX_CONFIGURED 0x1a #define MFI_STAT_LD_NOT_OPTIMAL 0x1b #define MFI_STAT_LD_RBLD_IN_PROGRESS 0x1c #define MFI_STAT_LD_RECON_IN_PROGRESS 0x1d #define MFI_STAT_LD_WRONG_RAID_LEVEL 0x1e #define MFI_STAT_MAX_SPARES_EXCEEDED 0x1f #define MFI_STAT_MEMORY_NOT_AVAILABLE 0x20 #define MFI_STAT_MFC_HW_ERROR 0x21 #define MFI_STAT_NO_HW_PRESENT 0x22 #define MFI_STAT_NOT_FOUND 0x23 #define MFI_STAT_NOT_IN_ENCL 0x24 #define MFI_STAT_PD_CLEAR_IN_PROGRESS 0x25 #define MFI_STAT_PD_TYPE_WRONG 0x26 #define MFI_STAT_PR_DISABLED 0x27 #define MFI_STAT_ROW_INDEX_INVALID 0x28 #define MFI_STAT_SAS_CFG_INVALID_ACTION 0x29 #define MFI_STAT_SAS_CFG_INVALID_DATA 0x2a #define MFI_STAT_SAS_CFG_INVALID_PAGE 0x2b #define MFI_STAT_SAS_CFG_INVALID_TYPE 0x2c #define MFI_STAT_SCSI_DONE_WITH_ERROR 0x2d #define MFI_STAT_SCSI_IO_FAILED 0x2e #define MFI_STAT_SCSI_RESERV_CONFLICT 0x2f #define MFI_STAT_SHUTDOWN_FAILED 0x30 #define MFI_STAT_TIME_NOT_SET 0x31 #define MFI_STAT_WRONG_STATE 0x32 #define MFI_STAT_LD_OFFLINE 0x33 #define MFI_STAT_PEER_NOTI_REJECTED 0x34 #define MFI_STAT_PEER_NOTI_FAILED 0x35 #define MFI_STAT_RESERV_IN_PROGRESS 0x36 #define MFI_STAT_I2C_ERRORS_DETECTED 0x37 #define MFI_STAT_PCI_ERRORS_DETECTED 0x38 #define MFI_STAT_INVALID_STATUS 0xff #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; #define MFI_CMD_SCSI_CDBLEN 16 uint8_t cdb[MFI_CMD_SCSI_CDBLEN]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 47 Prop-content-length: 146 Content-length: 146 K 7 svn:log V 43 commit what i have so it can be looked at. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T09:07:33.234082Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 27932 Text-content-md5: 2e0f76c6ec5b4aa7961f674b48ec23a5 Content-length: 27932 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = mpd->mpd_senselen; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD); switch (io->status) { case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_scbp[0] = 0; pkt->pkt_resid = 0; break; case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_reason = CMD_INCOMPLETE; pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; for (i = 0; i < mpd->mpd_senselen; i++) cmn_err(CE_NOTE, "sense[%d]: 0x%02x", i, ccb->ccb_extra[i]); break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 48 Prop-content-length: 206 Content-length: 206 K 7 svn:log V 102 properly deal with sense data. unfortunately this seems to cause lockups on attach of the sd device. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-16T12:07:52.342147Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 28425 Text-content-md5: 8db156f08d0b29f0d3f890d86f6220f5 Content-length: 28425 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 10, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = mpd->mpd_senselen; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb: 0x%02x", io->cdb[0]); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } #if 0 for (i = 0; i < mpd->mpd_senselen; i++) cmn_err(CE_NOTE, "sense[%d]: 0x%02x", i, ccb->ccb_extra[i]); #endif case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_getcap"); #endif return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { #if 0 cmn_err(CE_NOTE, "mfi_tran_setcap"); #endif return (-1); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 1); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 49 Prop-content-length: 1153 Content-length: 1153 K 7 svn:log V 1048 OMG IT WORKS!@! dlg@vavaea mfi$ sudo zpool create perc c3t0d0 dlg@vavaea mfi$ echo $? 0 dlg@vavaea mfi$ zpool status pool: perc state: ONLINE scrub: none requested config: NAME STATE READ WRITE CKSUM perc ONLINE 0 0 0 c3t0d0 ONLINE 0 0 0 errors: No known data errors dlg@vavaea mfi$ zfs list NAME USED AVAIL REFER MOUNTPOINT perc 75.5K 5.35T 24.5K /perc this is extremely messy. the relevant changes were: - an increase in the length of the sgl from 10 to 50 so buffers with a large number of chunks can be used. - a change to how many frames should be read when the command is submitted. all 8 will be read, no matter how many are used. this will be fixed soon. - some basic goop in getcap and setcap for the ARQ stuff. this last one somehow fixes things so that when the firmware provides sense data, the midlayer doesn't retry the command immediately. the firmware faulted in this situation for some reason. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T03:55:34.867235Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 29937 Text-content-md5: dba4c8d33dda4b2584644c2b1ec7fdca Content-length: 29937 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #define MFI_DEBUG #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; struct mfi_sge32 mpd_sgl[80]; uint_t mpd_datalen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; ddi_dma_cookie_t cookie; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { /* sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; */ sgl[i].len = mpd->mpd_sgl[i].len; sgl[i].addr = mpd->mpd_sgl[i].addr; /* datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); */ } io->datalen = mpd->mpd_datalen; cmn_err(CE_NOTE, "omsg0: 0x%08x", mfi_read(sc, MFI_OMSG0)); DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); for (i = 0; i < mpd->mpd_cdblen; i++) { DPRINTF(MFI_D_HBA, " cdb[%d]: 0x%02x", i, io->cdb[i]); } mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } #if 0 for (i = 0; i < mpd->mpd_senselen; i++) cmn_err(CE_NOTE, "sense[%d]: 0x%02x", i, ccb->ccb_extra[i]); #endif case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; default: cmn_err(CE_NOTE, "OMG status 0x%02x omsg0: 0x%08x", io->status, mfi_read(sc, MFI_OMSG0)); } pkt->pkt_comp(pkt); for (i = 0; i < 100; i++) { cmn_err(CE_NOTE, "omsg0: 0x%08x", mfi_read(sc, MFI_OMSG0)); if ((i % 10) == 0) delay(drv_usectohz(1000)); } return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; for (i = 0; i < mpd->mpd_ncookies; i++) { mpd->mpd_sgl[i].len = mpd->mpd_cookies.dmac_size; mpd->mpd_sgl[i].addr = mpd->mpd_cookies.dmac_address; ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } mpd->mpd_datalen = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); if ((timeout % 1000) == 0) cmn_err(CE_NOTE, "mfi_poll omsg0: 0x%08x", mfi_read(sc, MFI_OMSG0)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 50 Prop-content-length: 173 Content-length: 173 K 7 svn:log V 70 remove a ton of the debugging written to get the last commit working. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T04:16:33.685569Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 29374 Text-content-md5: 5890c82cf1687040f5eec388dfd64047 Content-length: 29374 /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; struct mfi_sge32 mpd_sgl[80]; uint_t mpd_datalen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; ddi_dma_cookie_t cookie; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { /* sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; */ sgl[i].len = mpd->mpd_sgl[i].len; sgl[i].addr = mpd->mpd_sgl[i].addr; /* datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); */ } io->datalen = mpd->mpd_datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; for (i = 0; i < mpd->mpd_ncookies; i++) { mpd->mpd_sgl[i].len = mpd->mpd_cookies.dmac_size; mpd->mpd_sgl[i].addr = mpd->mpd_cookies.dmac_address; ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } mpd->mpd_datalen = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 51 Prop-content-length: 125 Content-length: 125 K 7 svn:log V 22 enable the Id keyword K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T04:19:15.987793Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Prop-content-length: 35 Text-content-length: 29386 Text-content-md5: f13ac85ff9ceeb99278275a8b4a9815d Content-length: 29421 K 12 svn:keywords V 2 Id PROPS-END /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; struct mfi_sge32 mpd_sgl[80]; uint_t mpd_datalen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; ddi_dma_cookie_t cookie; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { /* sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; */ sgl[i].len = mpd->mpd_sgl[i].len; sgl[i].addr = mpd->mpd_sgl[i].addr; /* datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); */ } io->datalen = mpd->mpd_datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; for (i = 0; i < mpd->mpd_ncookies; i++) { mpd->mpd_sgl[i].len = mpd->mpd_cookies.dmac_size; mpd->mpd_sgl[i].addr = mpd->mpd_cookies.dmac_address; ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } mpd->mpd_datalen = bp->b_bcount; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Prop-content-length: 35 Text-content-length: 6751 Text-content-md5: 9450ce1e202a56d433f8ca9706199f2c Content-length: 6786 K 12 svn:keywords V 2 Id PROPS-END /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #define MFI_STAT_OK 0x00 #define MFI_STAT_INVALID_CMD 0x01 #define MFI_STAT_INVALID_DCMD 0x02 #define MFI_STAT_INVALID_PARAMETER 0x03 #define MFI_STAT_INVALID_SEQ_NUMBER 0x04 #define MFI_STAT_ABORT_NOT_POSSIBLE 0x05 #define MFI_STAT_APP_HOST_CODE_NOT_FND 0x06 #define MFI_STAT_APP_IN_USE 0x07 #define MFI_STAT_APP_NOT_INITIALIZED 0x08 #define MFI_STAT_ARRAY_INDEX_INVALID 0x09 #define MFI_STAT_ARRAY_ROW_NOT_EMPTY 0x0a #define MFI_STAT_CFG_RESOURCE_CONFLICT 0x0b #define MFI_STAT_DEVICE_NOT_FOUND 0x0c #define MFI_STAT_DRIVE_TOO_SMALL 0x0d #define MFI_STAT_FLASH_ALLOC_FAIL 0x0e #define MFI_STAT_FLASH_BUSY 0x0f #define MFI_STAT_FLASH_ERROR 0x10 #define MFI_STAT_FLASH_IMAGE_BAD 0x11 #define MFI_STAT_FLASH_IMAGE_INCOMPLETE 0x12 #define MFI_STAT_FLASH_NOT_OPEN 0x13 #define MFI_STAT_FLASH_NOT_STARTED 0x14 #define MFI_STAT_FLUSH_FAILED 0x15 #define MFI_STAT_HOST_CODE_NOT_FOUNT 0x16 #define MFI_STAT_LD_CC_IN_PROGRESS 0x17 #define MFI_STAT_LD_INIT_IN_PROGRESS 0x18 #define MFI_STAT_LD_LBA_OUT_OF_RANGE 0x19 #define MFI_STAT_LD_MAX_CONFIGURED 0x1a #define MFI_STAT_LD_NOT_OPTIMAL 0x1b #define MFI_STAT_LD_RBLD_IN_PROGRESS 0x1c #define MFI_STAT_LD_RECON_IN_PROGRESS 0x1d #define MFI_STAT_LD_WRONG_RAID_LEVEL 0x1e #define MFI_STAT_MAX_SPARES_EXCEEDED 0x1f #define MFI_STAT_MEMORY_NOT_AVAILABLE 0x20 #define MFI_STAT_MFC_HW_ERROR 0x21 #define MFI_STAT_NO_HW_PRESENT 0x22 #define MFI_STAT_NOT_FOUND 0x23 #define MFI_STAT_NOT_IN_ENCL 0x24 #define MFI_STAT_PD_CLEAR_IN_PROGRESS 0x25 #define MFI_STAT_PD_TYPE_WRONG 0x26 #define MFI_STAT_PR_DISABLED 0x27 #define MFI_STAT_ROW_INDEX_INVALID 0x28 #define MFI_STAT_SAS_CFG_INVALID_ACTION 0x29 #define MFI_STAT_SAS_CFG_INVALID_DATA 0x2a #define MFI_STAT_SAS_CFG_INVALID_PAGE 0x2b #define MFI_STAT_SAS_CFG_INVALID_TYPE 0x2c #define MFI_STAT_SCSI_DONE_WITH_ERROR 0x2d #define MFI_STAT_SCSI_IO_FAILED 0x2e #define MFI_STAT_SCSI_RESERV_CONFLICT 0x2f #define MFI_STAT_SHUTDOWN_FAILED 0x30 #define MFI_STAT_TIME_NOT_SET 0x31 #define MFI_STAT_WRONG_STATE 0x32 #define MFI_STAT_LD_OFFLINE 0x33 #define MFI_STAT_PEER_NOTI_REJECTED 0x34 #define MFI_STAT_PEER_NOTI_FAILED 0x35 #define MFI_STAT_RESERV_IN_PROGRESS 0x36 #define MFI_STAT_I2C_ERRORS_DETECTED 0x37 #define MFI_STAT_PCI_ERRORS_DETECTED 0x38 #define MFI_STAT_INVALID_STATUS 0xff #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; #define MFI_CMD_SCSI_CDBLEN 16 uint8_t cdb[MFI_CMD_SCSI_CDBLEN]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 52 Prop-content-length: 132 Content-length: 132 K 7 svn:log V 29 Add the Id property here too K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T04:23:38.066915Z PROPS-END Node-path: Makefile Node-kind: file Node-action: change Prop-content-length: 35 Text-content-length: 772 Text-content-md5: fdd9446c6f95989e4a62191f21fe3925 Content-length: 807 K 12 svn:keywords V 2 Id PROPS-END # $Id$ all: mfi clean: rm mfi.o mfi install: mfi sudo cp mfi /tmp load: sudo add_drv -i pci1028,15 -c scsi mfi unload: sudo rem_drv mfi reload: sudo rem_drv mfi sleep 1 sudo add_drv -i pci1028,15 -c scsi mfi mfi: mfi.o /usr/ccs/bin/ld -r mfi.o -o mfi mfi.o: mfi.c mfireg.h cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -c mfi.c # cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -c mfi.c #cc -D_KERNEL -xarch=amd64 -xmodel=kernel -errtags=yes -errwarn=%all -erroff=E_EMPTY_TRANSLATION_UNIT -erroff=E_STATEMENT_NOT_REACHED -xc99=%none -Wd,-xsafe=unboundsym -c mfi.c Revision-number: 53 Prop-content-length: 197 Content-length: 197 K 7 svn:log V 94 build the sgl in tran_start, rather than keeping a copy in mpd and copying it out every time. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T04:45:44.477577Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 28984 Text-content-md5: 76a3bf5f1264a1c5d37c60edfe748883 Content-length: 28984 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; ddi_dma_cookie_t cookie; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 54 Prop-content-length: 143 Content-length: 143 K 7 svn:log V 40 unused variable in init_pkt can go away K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T04:52:50.233428Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 28973 Text-content-md5: c214e11e049257c9ae08eae55316708e Content-length: 28973 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; int sc_sgllen; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); sc->sc_sgllen = MFI_OMSG0_MAXSGL(status); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; ddi_dma_cookie_t cookie; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 55 Prop-content-length: 164 Content-length: 164 K 7 svn:log V 61 properly figure out the max length of the sgls we can create K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T05:33:01.692772Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 29076 Text-content-md5: c145e360105a3590a220effc445be32e Content-length: 29076 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_ccb { uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; struct scsi_arq_status *arqstat; ddi_dma_cookie_t cookie; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); mfi_poll(sc, ccb, 10000); DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; } pkt->pkt_comp(pkt); return (TRAN_ACCEPT); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; mpd->mpd_ccb = ccb; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 6887 Text-content-md5: 9739bc40af2c55878370639c58e62af3 Content-length: 6887 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #define MFI_STAT_OK 0x00 #define MFI_STAT_INVALID_CMD 0x01 #define MFI_STAT_INVALID_DCMD 0x02 #define MFI_STAT_INVALID_PARAMETER 0x03 #define MFI_STAT_INVALID_SEQ_NUMBER 0x04 #define MFI_STAT_ABORT_NOT_POSSIBLE 0x05 #define MFI_STAT_APP_HOST_CODE_NOT_FND 0x06 #define MFI_STAT_APP_IN_USE 0x07 #define MFI_STAT_APP_NOT_INITIALIZED 0x08 #define MFI_STAT_ARRAY_INDEX_INVALID 0x09 #define MFI_STAT_ARRAY_ROW_NOT_EMPTY 0x0a #define MFI_STAT_CFG_RESOURCE_CONFLICT 0x0b #define MFI_STAT_DEVICE_NOT_FOUND 0x0c #define MFI_STAT_DRIVE_TOO_SMALL 0x0d #define MFI_STAT_FLASH_ALLOC_FAIL 0x0e #define MFI_STAT_FLASH_BUSY 0x0f #define MFI_STAT_FLASH_ERROR 0x10 #define MFI_STAT_FLASH_IMAGE_BAD 0x11 #define MFI_STAT_FLASH_IMAGE_INCOMPLETE 0x12 #define MFI_STAT_FLASH_NOT_OPEN 0x13 #define MFI_STAT_FLASH_NOT_STARTED 0x14 #define MFI_STAT_FLUSH_FAILED 0x15 #define MFI_STAT_HOST_CODE_NOT_FOUNT 0x16 #define MFI_STAT_LD_CC_IN_PROGRESS 0x17 #define MFI_STAT_LD_INIT_IN_PROGRESS 0x18 #define MFI_STAT_LD_LBA_OUT_OF_RANGE 0x19 #define MFI_STAT_LD_MAX_CONFIGURED 0x1a #define MFI_STAT_LD_NOT_OPTIMAL 0x1b #define MFI_STAT_LD_RBLD_IN_PROGRESS 0x1c #define MFI_STAT_LD_RECON_IN_PROGRESS 0x1d #define MFI_STAT_LD_WRONG_RAID_LEVEL 0x1e #define MFI_STAT_MAX_SPARES_EXCEEDED 0x1f #define MFI_STAT_MEMORY_NOT_AVAILABLE 0x20 #define MFI_STAT_MFC_HW_ERROR 0x21 #define MFI_STAT_NO_HW_PRESENT 0x22 #define MFI_STAT_NOT_FOUND 0x23 #define MFI_STAT_NOT_IN_ENCL 0x24 #define MFI_STAT_PD_CLEAR_IN_PROGRESS 0x25 #define MFI_STAT_PD_TYPE_WRONG 0x26 #define MFI_STAT_PR_DISABLED 0x27 #define MFI_STAT_ROW_INDEX_INVALID 0x28 #define MFI_STAT_SAS_CFG_INVALID_ACTION 0x29 #define MFI_STAT_SAS_CFG_INVALID_DATA 0x2a #define MFI_STAT_SAS_CFG_INVALID_PAGE 0x2b #define MFI_STAT_SAS_CFG_INVALID_TYPE 0x2c #define MFI_STAT_SCSI_DONE_WITH_ERROR 0x2d #define MFI_STAT_SCSI_IO_FAILED 0x2e #define MFI_STAT_SCSI_RESERV_CONFLICT 0x2f #define MFI_STAT_SHUTDOWN_FAILED 0x30 #define MFI_STAT_TIME_NOT_SET 0x31 #define MFI_STAT_WRONG_STATE 0x32 #define MFI_STAT_LD_OFFLINE 0x33 #define MFI_STAT_PEER_NOTI_REJECTED 0x34 #define MFI_STAT_PEER_NOTI_FAILED 0x35 #define MFI_STAT_RESERV_IN_PROGRESS 0x36 #define MFI_STAT_I2C_ERRORS_DETECTED 0x37 #define MFI_STAT_PCI_ERRORS_DETECTED 0x38 #define MFI_STAT_INVALID_STATUS 0xff #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; /* * the max length of the sgl is: * ((FRAME_SIZE * FRAME_COUNT) - sizeof(scsi_cmd)) / sizeof(sge32) */ #define MFI_MAX_SGL_LEN 58 struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; #define MFI_CMD_SCSI_CDBLEN 16 uint8_t cdb[MFI_CMD_SCSI_CDBLEN]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 56 Prop-content-length: 299 Content-length: 299 K 7 svn:log V 195 introduce a completion path for ccbs. these will be dispatched to the task queues out of the interrupt handler. polled io will run it directly. the fwinit and scsi io paths are already using it. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-17T07:46:07.253661Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 29953 Text-content-md5: 70017f72964d1123d2977431b0282c59 Content-length: 29953 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc; struct mfi_pkt_data; struct mfi_ccb { struct mfi_softc *ccb_sc; struct mfi_pkt_data *ccb_mpd; void (*ccb_done)(void *); uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; struct scsi_pkt *mpd_pkt; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static void mfi_ccb_done(void *); static void mfi_ccb_done_tran(void *); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto free_ccbs; } return (DDI_SUCCESS); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); ccb->ccb_done = mfi_ccb_done; init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_done = mfi_ccb_done_tran; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; if (mfi_poll(sc, ccb, 10000) != DDI_SUCCESS) return (TRAN_FATAL_ERROR); return (TRAN_ACCEPT); } static void mfi_ccb_done_tran(void *arg) { struct mfi_ccb *ccb = arg; struct mfi_softc *sc = ccb->ccb_sc; struct mfi_pkt_data *mpd = ccb->ccb_mpd; struct mfi_cmd_scsi *io; struct scsi_pkt *pkt = mpd->mpd_pkt; struct scsi_arq_status *arqstat; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; /* XXX deal with other values */ } pkt->pkt_comp(pkt); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb->ccb_mpd = mpd; mpd->mpd_ccb = ccb; mpd->mpd_pkt = pkt; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } /* dont add this to a taskq, just run it directly */ ccb->ccb_done(ccb); return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static void mfi_ccb_done(void *arg) { /* empty handler handler */ } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_sc = sc; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_mpd = NULL; ccb->ccb_done = NULL; ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 57 Prop-content-length: 487 Content-length: 487 K 7 svn:log V 383 implement an interrupt handler, and allow scsi io to happen using interrupts. this would have been simple if i didnt have MFI_FWINIT_PTRS64 set in the fwinit message. it seems it means that the message context is 64bits as well as the poi nters to the various parts of the reply queue handling. i can deal with that tho ugh. initialise the reply q parts to sane values while here. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-20T04:26:27.191928Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 31538 Text-content-md5: c9a3998e79b58569b30faabd9bb71852 Content-length: 31538 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc; struct mfi_pkt_data; struct mfi_ccb { struct mfi_softc *ccb_sc; struct mfi_pkt_data *ccb_mpd; void (*ccb_done)(void *); uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; struct scsi_pkt *mpd_pkt; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; static uint_t mfi_intr(caddr_t); #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static void mfi_ccb_done(void *); static void mfi_ccb_done_tran(void *); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (ddi_add_intr(sc->sc_dev, 0, &sc->sc_iblock_cookie, NULL, mfi_intr, (caddr_t)sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to establish interrupt"); goto free_ccbs; } mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR); if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto del_intr; } return (DDI_SUCCESS); del_intr: ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static uint_t mfi_intr(caddr_t arg) { struct mfi_softc *sc = (struct mfi_softc *)arg; struct mfi_ccb *ccb; uint32_t status, producer, consumer, context; uint_t rv = DDI_INTR_UNCLAIMED; if (mutex_tryenter(&sc->sc_replyq_mutex) == 0) return (DDI_INTR_UNCLAIMED); status = mfi_read(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) { mutex_exit(&sc->sc_replyq_mutex); return (DDI_INTR_UNCLAIMED); } mfi_write(sc, MFI_OSTS, status); producer = sc->sc_pc->producer; consumer = sc->sc_pc->consumer; while (consumer != producer) { context = sc->sc_replyq[consumer]; /* XXX check consumer is < ncmds */ if (context != 0xffffffff) { ccb = &sc->sc_ccbs[context]; if (ddi_taskq_dispatch(sc->sc_taskq, ccb->ccb_done, ccb, DDI_NOSLEEP) != DDI_SUCCESS) break; sc->sc_replyq[consumer] = 0xffffffff; } rv = DDI_INTR_CLAIMED; consumer++; consumer %= sc->sc_ncmds + 1; } sc->sc_pc->consumer = consumer; mutex_exit(&sc->sc_replyq_mutex); return (rv); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); ccb->ccb_done = mfi_ccb_done; init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); // qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; int i; int datalen = 0; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_done = mfi_ccb_done_tran; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } io->datalen = datalen; DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; if (pkt->pkt_flags & FLAG_NOINTR) { if (mfi_poll(sc, ccb, 10000) != DDI_SUCCESS) return (TRAN_FATAL_ERROR); } else mfi_post(sc, ccb); return (TRAN_ACCEPT); } static void mfi_ccb_done_tran(void *arg) { struct mfi_ccb *ccb = arg; struct mfi_softc *sc = ccb->ccb_sc; struct mfi_pkt_data *mpd = ccb->ccb_mpd; struct mfi_cmd_scsi *io; struct scsi_pkt *pkt = mpd->mpd_pkt; struct scsi_arq_status *arqstat; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; /* XXX deal with other values */ } pkt->pkt_comp(pkt); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb->ccb_mpd = mpd; mpd->mpd_ccb = ccb; mpd->mpd_pkt = pkt; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } /* dont add this to a taskq, just run it directly */ ccb->ccb_done(ccb); return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static void mfi_ccb_done(void *arg) { /* empty handler handler */ } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_pc->consumer = 0x0; sc->sc_pc->producer = 0x0; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); memset(sc->sc_replyq, 0xff, (sc->sc_ncmds + 1) * sizeof(uint32_t)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_sc = sc; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_mpd = NULL; ccb->ccb_done = NULL; ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 6966 Text-content-md5: 0b9a42cd46ca42138fcc189bb22f55bc Content-length: 6966 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_OSTS_INTR_VALID 0x00000002 #define MFI_ENABLE_INTR 0x00000001 #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #define MFI_STAT_OK 0x00 #define MFI_STAT_INVALID_CMD 0x01 #define MFI_STAT_INVALID_DCMD 0x02 #define MFI_STAT_INVALID_PARAMETER 0x03 #define MFI_STAT_INVALID_SEQ_NUMBER 0x04 #define MFI_STAT_ABORT_NOT_POSSIBLE 0x05 #define MFI_STAT_APP_HOST_CODE_NOT_FND 0x06 #define MFI_STAT_APP_IN_USE 0x07 #define MFI_STAT_APP_NOT_INITIALIZED 0x08 #define MFI_STAT_ARRAY_INDEX_INVALID 0x09 #define MFI_STAT_ARRAY_ROW_NOT_EMPTY 0x0a #define MFI_STAT_CFG_RESOURCE_CONFLICT 0x0b #define MFI_STAT_DEVICE_NOT_FOUND 0x0c #define MFI_STAT_DRIVE_TOO_SMALL 0x0d #define MFI_STAT_FLASH_ALLOC_FAIL 0x0e #define MFI_STAT_FLASH_BUSY 0x0f #define MFI_STAT_FLASH_ERROR 0x10 #define MFI_STAT_FLASH_IMAGE_BAD 0x11 #define MFI_STAT_FLASH_IMAGE_INCOMPLETE 0x12 #define MFI_STAT_FLASH_NOT_OPEN 0x13 #define MFI_STAT_FLASH_NOT_STARTED 0x14 #define MFI_STAT_FLUSH_FAILED 0x15 #define MFI_STAT_HOST_CODE_NOT_FOUNT 0x16 #define MFI_STAT_LD_CC_IN_PROGRESS 0x17 #define MFI_STAT_LD_INIT_IN_PROGRESS 0x18 #define MFI_STAT_LD_LBA_OUT_OF_RANGE 0x19 #define MFI_STAT_LD_MAX_CONFIGURED 0x1a #define MFI_STAT_LD_NOT_OPTIMAL 0x1b #define MFI_STAT_LD_RBLD_IN_PROGRESS 0x1c #define MFI_STAT_LD_RECON_IN_PROGRESS 0x1d #define MFI_STAT_LD_WRONG_RAID_LEVEL 0x1e #define MFI_STAT_MAX_SPARES_EXCEEDED 0x1f #define MFI_STAT_MEMORY_NOT_AVAILABLE 0x20 #define MFI_STAT_MFC_HW_ERROR 0x21 #define MFI_STAT_NO_HW_PRESENT 0x22 #define MFI_STAT_NOT_FOUND 0x23 #define MFI_STAT_NOT_IN_ENCL 0x24 #define MFI_STAT_PD_CLEAR_IN_PROGRESS 0x25 #define MFI_STAT_PD_TYPE_WRONG 0x26 #define MFI_STAT_PR_DISABLED 0x27 #define MFI_STAT_ROW_INDEX_INVALID 0x28 #define MFI_STAT_SAS_CFG_INVALID_ACTION 0x29 #define MFI_STAT_SAS_CFG_INVALID_DATA 0x2a #define MFI_STAT_SAS_CFG_INVALID_PAGE 0x2b #define MFI_STAT_SAS_CFG_INVALID_TYPE 0x2c #define MFI_STAT_SCSI_DONE_WITH_ERROR 0x2d #define MFI_STAT_SCSI_IO_FAILED 0x2e #define MFI_STAT_SCSI_RESERV_CONFLICT 0x2f #define MFI_STAT_SHUTDOWN_FAILED 0x30 #define MFI_STAT_TIME_NOT_SET 0x31 #define MFI_STAT_WRONG_STATE 0x32 #define MFI_STAT_LD_OFFLINE 0x33 #define MFI_STAT_PEER_NOTI_REJECTED 0x34 #define MFI_STAT_PEER_NOTI_FAILED 0x35 #define MFI_STAT_RESERV_IN_PROGRESS 0x36 #define MFI_STAT_I2C_ERRORS_DETECTED 0x37 #define MFI_STAT_PCI_ERRORS_DETECTED 0x38 #define MFI_STAT_INVALID_STATUS 0xff #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; /* * the max length of the sgl is: * ((FRAME_SIZE * FRAME_COUNT) - sizeof(scsi_cmd)) / sizeof(sge32) */ #define MFI_MAX_SGL_LEN 58 struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; #define MFI_CMD_SCSI_CDBLEN 16 uint8_t cdb[MFI_CMD_SCSI_CDBLEN]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 58 Prop-content-length: 373 Content-length: 373 K 7 svn:log V 269 split the tran_start path up into bits so we can handle READ and WRITE commands separately to the rest of the scsi commands. filling in the command frame and the sgl are now pulled apart. there isnt a path for the READs and WRITEs yet, they still go via the scsi path. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-20T06:02:12.386768Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 32575 Text-content-md5: 58bac7e806415472e396de017162c2fd Content-length: 32575 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc; struct mfi_pkt_data; struct mfi_ccb { struct mfi_softc *ccb_sc; struct mfi_pkt_data *ccb_mpd; void (*ccb_done)(void *); uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; struct scsi_pkt *mpd_pkt; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; static uint_t mfi_intr(caddr_t); #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static void mfi_done(void *); static uint32_t mfi_load_sgl(struct mfi_pkt_data *, struct mfi_sge32 *); static void mfi_start_io(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_start_scsi(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_done_scsi(void *); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (ddi_add_intr(sc->sc_dev, 0, &sc->sc_iblock_cookie, NULL, mfi_intr, (caddr_t)sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to establish interrupt"); goto free_ccbs; } mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR); if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto del_intr; } return (DDI_SUCCESS); del_intr: ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static uint_t mfi_intr(caddr_t arg) { struct mfi_softc *sc = (struct mfi_softc *)arg; struct mfi_ccb *ccb; uint32_t status, producer, consumer, context; uint_t rv = DDI_INTR_UNCLAIMED; if (mutex_tryenter(&sc->sc_replyq_mutex) == 0) return (DDI_INTR_UNCLAIMED); status = mfi_read(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) { mutex_exit(&sc->sc_replyq_mutex); return (DDI_INTR_UNCLAIMED); } mfi_write(sc, MFI_OSTS, status); producer = sc->sc_pc->producer; consumer = sc->sc_pc->consumer; while (consumer != producer) { context = sc->sc_replyq[consumer]; /* XXX check consumer is < ncmds */ if (context != 0xffffffff) { ccb = &sc->sc_ccbs[context]; if (ddi_taskq_dispatch(sc->sc_taskq, ccb->ccb_done, ccb, DDI_NOSLEEP) != DDI_SUCCESS) break; sc->sc_replyq[consumer] = 0xffffffff; } rv = DDI_INTR_CLAIMED; consumer++; consumer %= sc->sc_ncmds + 1; } sc->sc_pc->consumer = consumer; mutex_exit(&sc->sc_replyq_mutex); return (rv); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); ccb->ccb_done = mfi_done; init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); // qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; union scsi_cdb *cdb; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); cdb = (union scsi_cdb *)pkt->pkt_cdbp; switch (cdb->scc_cmd) { #if notyet case SCMD_READ: case SCMD_READ_G1: case SCMD_READ_G4: case SCMD_WRITE: case SCMD_WRITE_G1: case SCMD_WRITE_G4: /* G5 READS AND WRITES? */ mfi_start_io(sc, mpd); break; #endif default: mfi_start_scsi(sc, ap, mpd); break; } pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; if (pkt->pkt_flags & FLAG_NOINTR) { if (mfi_poll(sc, ccb, 10000) != DDI_SUCCESS) return (TRAN_FATAL_ERROR); } else mfi_post(sc, ccb); return (TRAN_ACCEPT); } static void mfi_start_scsi(struct mfi_softc *sc, struct scsi_address *ap, struct mfi_pkt_data *mpd) { struct scsi_pkt *pkt = mpd->mpd_pkt; struct mfi_ccb *ccb = mpd->mpd_ccb; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); ccb->ccb_done = mfi_done_scsi; io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; /* always lun 0 */ io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); io->datalen = mfi_load_sgl(mpd, sgl); DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); } static void mfi_start_io(struct mfi_softc *sc, struct scsi_address *ap, struct mfi_pkt_data *mpd) { } static uint32_t mfi_load_sgl(struct mfi_pkt_data *mpd, struct mfi_sge32 *sgl) { int i; uint32_t datalen; for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } return (datalen); } static void mfi_done_scsi(void *arg) { struct mfi_ccb *ccb = arg; struct mfi_softc *sc = ccb->ccb_sc; struct mfi_pkt_data *mpd = ccb->ccb_mpd; struct mfi_cmd_scsi *io; struct scsi_pkt *pkt = mpd->mpd_pkt; struct scsi_arq_status *arqstat; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; /* XXX deal with other values */ } pkt->pkt_comp(pkt); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb->ccb_mpd = mpd; mpd->mpd_ccb = ccb; mpd->mpd_pkt = pkt; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } /* dont add this to a taskq, just run it directly */ ccb->ccb_done(ccb); return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static void mfi_done(void *arg) { /* empty handler handler */ } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_pc->consumer = 0x0; sc->sc_pc->producer = 0x0; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); memset(sc->sc_replyq, 0xff, (sc->sc_ncmds + 1) * sizeof(uint32_t)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_sc = sc; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_mpd = NULL; ccb->ccb_done = NULL; ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 59 Prop-content-length: 246 Content-length: 246 K 7 svn:log V 142 do reads and writes using the ld read and write commands. this is supposedly faster, but i havent seen a huge amount of improvement. oh well. K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-20T07:33:14.402347Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 33610 Text-content-md5: 0568f00ff6c9eff6e9b78eca150bcea5 Content-length: 33610 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc; struct mfi_pkt_data; struct mfi_ccb { struct mfi_softc *ccb_sc; struct mfi_pkt_data *ccb_mpd; void (*ccb_done)(void *); uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; struct scsi_pkt *mpd_pkt; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; static uint_t mfi_intr(caddr_t); #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static void mfi_done(void *); static uint32_t mfi_load_sgl(struct mfi_pkt_data *, struct mfi_sge32 *); static void mfi_start_io(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_start_scsi(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_done_tran(void *); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (ddi_add_intr(sc->sc_dev, 0, &sc->sc_iblock_cookie, NULL, mfi_intr, (caddr_t)sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to establish interrupt"); goto free_ccbs; } mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR); if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto del_intr; } return (DDI_SUCCESS); del_intr: ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static uint_t mfi_intr(caddr_t arg) { struct mfi_softc *sc = (struct mfi_softc *)arg; struct mfi_ccb *ccb; uint32_t status, producer, consumer, context; uint_t rv = DDI_INTR_UNCLAIMED; if (mutex_tryenter(&sc->sc_replyq_mutex) == 0) return (DDI_INTR_UNCLAIMED); status = mfi_read(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) { mutex_exit(&sc->sc_replyq_mutex); return (DDI_INTR_UNCLAIMED); } mfi_write(sc, MFI_OSTS, status); producer = sc->sc_pc->producer; consumer = sc->sc_pc->consumer; while (consumer != producer) { context = sc->sc_replyq[consumer]; /* XXX check consumer is < ncmds */ if (context != 0xffffffff) { ccb = &sc->sc_ccbs[context]; if (ddi_taskq_dispatch(sc->sc_taskq, ccb->ccb_done, ccb, DDI_NOSLEEP) != DDI_SUCCESS) break; sc->sc_replyq[consumer] = 0xffffffff; } rv = DDI_INTR_CLAIMED; consumer++; consumer %= sc->sc_ncmds + 1; } sc->sc_pc->consumer = consumer; mutex_exit(&sc->sc_replyq_mutex); return (rv); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); ccb->ccb_done = mfi_done; init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); // qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; union scsi_cdb *cdb; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_done = mfi_done_tran; cdb = (union scsi_cdb *)pkt->pkt_cdbp; switch (cdb->scc_cmd) { case SCMD_READ: case SCMD_READ_G1: case SCMD_READ_G4: case SCMD_WRITE: case SCMD_WRITE_G1: case SCMD_WRITE_G4: /* G5 READS AND WRITES? */ mfi_start_io(sc, ap, mpd); break; default: mfi_start_scsi(sc, ap, mpd); break; } pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; if (pkt->pkt_flags & FLAG_NOINTR) { if (mfi_poll(sc, ccb, 10000) != DDI_SUCCESS) return (TRAN_FATAL_ERROR); } else mfi_post(sc, ccb); return (TRAN_ACCEPT); } static void mfi_start_scsi(struct mfi_softc *sc, struct scsi_address *ap, struct mfi_pkt_data *mpd) { struct scsi_pkt *pkt = mpd->mpd_pkt; struct mfi_ccb *ccb = mpd->mpd_ccb; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; /* always lun 0 */ io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); io->datalen = mfi_load_sgl(mpd, sgl); DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); } static void mfi_start_io(struct mfi_softc *sc, struct scsi_address *ap, struct mfi_pkt_data *mpd) { struct scsi_pkt *pkt = mpd->mpd_pkt; struct mfi_ccb *ccb = mpd->mpd_ccb; struct mfi_cmd_io *io; struct mfi_sge32 *sgl; union scsi_cdb *cdb; io = (struct mfi_cmd_io *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_io)); io->cmd = mpd->mpd_read ? MFI_CMD_LD_READ : MFI_CMD_LD_WRITE; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); cdb = (union scsi_cdb *)pkt->pkt_cdbp; switch (cdb->scc_cmd) { case SCMD_READ: case SCMD_WRITE: io->block_lo = GETG0ADDR(cdb); break; case SCMD_READ_G1: case SCMD_WRITE_G1: io->block_lo = GETG1ADDR(cdb); break; case SCMD_READ_G4: case SCMD_WRITE_G4: io->block_lo = GETG4ADDRTL(cdb); io->block_hi = GETG4ADDR(cdb); break; } io->nblocks = (mfi_load_sgl(mpd, sgl) + 512 - 1) / 512; /* XXX magic */ } static uint32_t mfi_load_sgl(struct mfi_pkt_data *mpd, struct mfi_sge32 *sgl) { int i; uint32_t datalen; for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } return (datalen); } static void mfi_done_tran(void *arg) { struct mfi_ccb *ccb = arg; struct mfi_softc *sc = ccb->ccb_sc; struct mfi_pkt_data *mpd = ccb->ccb_mpd; struct mfi_cmd_hdr *io; struct scsi_pkt *pkt = mpd->mpd_pkt; struct scsi_arq_status *arqstat; io = (struct mfi_cmd_hdr *)ccb->ccb_cmd; DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; default: cmn_err(CE_NOTE, "%s: %d", __func__, io->status); break; /* XXX deal with other values */ } pkt->pkt_comp(pkt); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb->ccb_mpd = mpd; mpd->mpd_ccb = ccb; mpd->mpd_pkt = pkt; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } /* dont add this to a taskq, just run it directly */ ccb->ccb_done(ccb); return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static void mfi_done(void *arg) { /* empty handler handler */ } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_pc->consumer = 0x0; sc->sc_pc->producer = 0x0; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); memset(sc->sc_replyq, 0xff, (sc->sc_ncmds + 1) * sizeof(uint32_t)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_sc = sc; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb %d\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_pot_ccb: ccb %d\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_mpd = NULL; ccb->ccb_done = NULL; ccb->ccb_extra_frames = 0; mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Node-path: mfireg.h Node-kind: file Node-action: change Text-content-length: 7353 Text-content-md5: 90945f2320f7b8e06fb3b2b804bed928 Content-length: 7353 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ /* register offsets */ #define MFI_IMSG0 0x10 /* inbound message reg 0 */ #define MFI_IMSG1 0x14 /* inbound message reg 1 */ #define MFI_OMSG0 0x18 /* outbound msg 0 */ #define MFI_OMSG1 0x1c /* outbound msg 1 */ #define MFI_IDB 0x20 /* inbound doorbell */ #define MFI_ISTS 0x24 /* inbound intr stat */ #define MFI_IMSK 0x28 /* inbound intr mask */ #define MFI_ODB 0x2c /* outbound doorbell */ #define MFI_OSTS 0x30 /* outbound intr stat */ #define MFI_OMSK 0x34 /* outbound intr mask */ #define MFI_IQP 0x40 /* inbound queue port */ #define MFI_OQP 0x44 /* outbound queue port */ /* bits for the OMSG0 register */ #define MFI_OMSG0_FWSTATE 0xf0000000 #define MFI_FWSTATE_UNDEFINED 0x00000000 #define MFI_FWSTATE_BB_INIT 0x10000000 #define MFI_FWSTATE_FW_INIT 0x40000000 #define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000 #define MFI_FWSTATE_FW_INIT_2 0x70000000 #define MFI_FWSTATE_DEVICE_SCAN 0x80000000 #define MFI_FWSTATE_FLUSH_CACHE 0xa0000000 #define MFI_FWSTATE_READY 0xb0000000 #define MFI_FWSTATE_OPERATIONAL 0xc0000000 #define MFI_FWSTATE_FAULT 0xf0000000 #define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000 #define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff #define MFI_OMSG0_MAXSGL(r) ((r & 0x00ff0000) >> 16) #define MFI_OMSG0_MAXCMD(r) ((r & 0x0000ffff) >> 0) /* inbound doorbell */ /* command reset register */ #define MFI_IDB_ABORT 0x00000000 #define MFI_IDB_INIT_READY 0x00000002 #define MFI_IDB_MFIMODE 0x00000004 #define MFI_IDB_CLEAR_HANDSHAKE 0x00000008 #define MFI_IDB_RESET_FLAGS (MFI_INIT_READY|MFI_INIT_MFIMODE) #define MFI_OSTS_INTR_VALID 0x00000002 #define MFI_ENABLE_INTR 0x00000001 #define MFI_FRAME_SIZE 64 /* message frames are 64 bytes */ #define MFI_FRAME_COUNT 8 #define MFI_CMD_FWINIT 0x00 #define MFI_CMD_LD_READ 0x01 #define MFI_CMD_LD_WRITE 0x02 #define MFI_CMD_LD_SCSI 0x03 #define MFI_STAT_OK 0x00 #define MFI_STAT_INVALID_CMD 0x01 #define MFI_STAT_INVALID_DCMD 0x02 #define MFI_STAT_INVALID_PARAMETER 0x03 #define MFI_STAT_INVALID_SEQ_NUMBER 0x04 #define MFI_STAT_ABORT_NOT_POSSIBLE 0x05 #define MFI_STAT_APP_HOST_CODE_NOT_FND 0x06 #define MFI_STAT_APP_IN_USE 0x07 #define MFI_STAT_APP_NOT_INITIALIZED 0x08 #define MFI_STAT_ARRAY_INDEX_INVALID 0x09 #define MFI_STAT_ARRAY_ROW_NOT_EMPTY 0x0a #define MFI_STAT_CFG_RESOURCE_CONFLICT 0x0b #define MFI_STAT_DEVICE_NOT_FOUND 0x0c #define MFI_STAT_DRIVE_TOO_SMALL 0x0d #define MFI_STAT_FLASH_ALLOC_FAIL 0x0e #define MFI_STAT_FLASH_BUSY 0x0f #define MFI_STAT_FLASH_ERROR 0x10 #define MFI_STAT_FLASH_IMAGE_BAD 0x11 #define MFI_STAT_FLASH_IMAGE_INCOMPLETE 0x12 #define MFI_STAT_FLASH_NOT_OPEN 0x13 #define MFI_STAT_FLASH_NOT_STARTED 0x14 #define MFI_STAT_FLUSH_FAILED 0x15 #define MFI_STAT_HOST_CODE_NOT_FOUNT 0x16 #define MFI_STAT_LD_CC_IN_PROGRESS 0x17 #define MFI_STAT_LD_INIT_IN_PROGRESS 0x18 #define MFI_STAT_LD_LBA_OUT_OF_RANGE 0x19 #define MFI_STAT_LD_MAX_CONFIGURED 0x1a #define MFI_STAT_LD_NOT_OPTIMAL 0x1b #define MFI_STAT_LD_RBLD_IN_PROGRESS 0x1c #define MFI_STAT_LD_RECON_IN_PROGRESS 0x1d #define MFI_STAT_LD_WRONG_RAID_LEVEL 0x1e #define MFI_STAT_MAX_SPARES_EXCEEDED 0x1f #define MFI_STAT_MEMORY_NOT_AVAILABLE 0x20 #define MFI_STAT_MFC_HW_ERROR 0x21 #define MFI_STAT_NO_HW_PRESENT 0x22 #define MFI_STAT_NOT_FOUND 0x23 #define MFI_STAT_NOT_IN_ENCL 0x24 #define MFI_STAT_PD_CLEAR_IN_PROGRESS 0x25 #define MFI_STAT_PD_TYPE_WRONG 0x26 #define MFI_STAT_PR_DISABLED 0x27 #define MFI_STAT_ROW_INDEX_INVALID 0x28 #define MFI_STAT_SAS_CFG_INVALID_ACTION 0x29 #define MFI_STAT_SAS_CFG_INVALID_DATA 0x2a #define MFI_STAT_SAS_CFG_INVALID_PAGE 0x2b #define MFI_STAT_SAS_CFG_INVALID_TYPE 0x2c #define MFI_STAT_SCSI_DONE_WITH_ERROR 0x2d #define MFI_STAT_SCSI_IO_FAILED 0x2e #define MFI_STAT_SCSI_RESERV_CONFLICT 0x2f #define MFI_STAT_SHUTDOWN_FAILED 0x30 #define MFI_STAT_TIME_NOT_SET 0x31 #define MFI_STAT_WRONG_STATE 0x32 #define MFI_STAT_LD_OFFLINE 0x33 #define MFI_STAT_PEER_NOTI_REJECTED 0x34 #define MFI_STAT_PEER_NOTI_FAILED 0x35 #define MFI_STAT_RESERV_IN_PROGRESS 0x36 #define MFI_STAT_I2C_ERRORS_DETECTED 0x37 #define MFI_STAT_PCI_ERRORS_DETECTED 0x38 #define MFI_STAT_INVALID_STATUS 0xff #pragma pack(1) struct mfi_sge32 { uint32_t addr; uint32_t len; }; struct mfi_sge64 { uint32_t addr_lo; uint32_t addr_hi; }; /* * the max length of the sgl is: * ((FRAME_SIZE * FRAME_COUNT) - sizeof(scsi_cmd)) / sizeof(sge32) */ #define MFI_MAX_SGL_LEN 58 struct mfi_cmd_hdr { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; #define MFI_CMD_FLAG_NO_POST_IN_REPLYQ (1<<0) #define MFI_CMD_FLAG_SGL64 (1<<1) #define MFI_CMD_FLAG_SENSE64 (1<<2) #define MFI_CMD_FLAG_WRITE (1<<2) #define MFI_CMD_FLAG_READ (1<<4) uint16_t timeout; uint32_t datalen; }; struct mfi_cmd_fwinit { uint8_t cmd; uint8_t reserved1; uint8_t status; uint8_t reserved2; uint32_t reserved3; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t reserved4; uint32_t datalen; uint32_t qinfo_new_lo; uint32_t qinfo_new_hi; uint32_t qinfo_old_lo; uint32_t qinfo_old_hi; }; struct mfi_fwinit_qinfo { uint32_t init_flags; #define MFI_FWINIT_MODE64 (1<<0) #define MFI_FWINIT_PTRS64 (1<<1) #define MFI_FWINIT_CMDSYNC (1<<2) uint32_t q_entries; uint32_t q_addr_lo; uint32_t q_addr_hi; uint32_t prod_lo; uint32_t prod_hi; uint32_t cons_lo; uint32_t cons_hi; }; struct mfi_cmd_io { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t access_byte; uint8_t reserved; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t nblocks; uint32_t sense_lo; uint32_t sense_hi; uint32_t block_lo; uint32_t block_hi; /* followed by an sgl */ }; struct mfi_cmd_scsi { uint8_t cmd; uint8_t sense_len; uint8_t status; uint8_t scsi_status; uint8_t target; uint8_t lun; uint8_t cdb_len; uint8_t nsge; uint32_t context; uint32_t context_hi; uint16_t flags; uint16_t timeout; uint32_t datalen; uint32_t sense_lo; uint32_t sense_hi; #define MFI_CMD_SCSI_CDBLEN 16 uint8_t cdb[MFI_CMD_SCSI_CDBLEN]; /* followed by an sgl */ }; #pragma pack() struct mfi_prod_cons { uint32_t producer; uint32_t consumer; }; Revision-number: 60 Prop-content-length: 211 Content-length: 211 K 7 svn:log V 107 if there are not ccbs on the free list, dont try to allocate a cmd buffer for it. return null in that case K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-20T08:12:37.686060Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 33933 Text-content-md5: 9e37a0a7f0ab37932f8dd493cb28ebcc Content-length: 33933 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc; struct mfi_pkt_data; struct mfi_ccb { struct mfi_softc *ccb_sc; struct mfi_pkt_data *ccb_mpd; void (*ccb_done)(void *); uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; struct scsi_pkt *mpd_pkt; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_requestq_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; static uint_t mfi_intr(caddr_t); #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_alloc_ccb(struct mfi_softc *); static void mfi_free_ccb(struct mfi_softc *, struct mfi_ccb *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static void mfi_done(void *); static uint32_t mfi_load_sgl(struct mfi_pkt_data *, struct mfi_sge32 *); static void mfi_start_io(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_start_scsi(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_done_tran(void *); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_requestq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (ddi_add_intr(sc->sc_dev, 0, &sc->sc_iblock_cookie, NULL, mfi_intr, (caddr_t)sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to establish interrupt"); goto free_ccbs; } mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR); if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto del_intr; } return (DDI_SUCCESS); del_intr: ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_requestq_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static uint_t mfi_intr(caddr_t arg) { struct mfi_softc *sc = (struct mfi_softc *)arg; struct mfi_ccb *ccb; uint32_t status, producer, consumer, context; uint_t rv = DDI_INTR_UNCLAIMED; if (mutex_tryenter(&sc->sc_replyq_mutex) == 0) return (DDI_INTR_UNCLAIMED); status = mfi_read(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) { mutex_exit(&sc->sc_replyq_mutex); return (DDI_INTR_UNCLAIMED); } producer = sc->sc_pc->producer; consumer = sc->sc_pc->consumer; while (consumer != producer) { context = sc->sc_replyq[consumer]; /* XXX check consumer is < ncmds */ if (context != 0xffffffff) { ccb = &sc->sc_ccbs[context]; if (ddi_taskq_dispatch(sc->sc_taskq, ccb->ccb_done, ccb, DDI_NOSLEEP) != DDI_SUCCESS) break; sc->sc_replyq[consumer] = 0xffffffff; } rv = DDI_INTR_CLAIMED; consumer++; consumer %= sc->sc_ncmds + 1; } sc->sc_pc->consumer = consumer; if (consumer == producer) mfi_write(sc, MFI_OSTS, status); mutex_exit(&sc->sc_replyq_mutex); return (rv); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); ccb->ccb_done = mfi_done; init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); // qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); } static int mfi_tran_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, scsi_hba_tran_t *tran, struct scsi_device *sd) { /* ld read/write dont take a lun, so only 0 is supported */ if (sd->sd_address.a_lun != 0) return (DDI_FAILURE); if (sd->sd_address.a_target != 0) return (DDI_FAILURE); return (DDI_SUCCESS); } static int mfi_tran_start(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; union scsi_cdb *cdb; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_done = mfi_done_tran; cdb = (union scsi_cdb *)pkt->pkt_cdbp; switch (cdb->scc_cmd) { case SCMD_READ: case SCMD_READ_G1: case SCMD_READ_G4: case SCMD_WRITE: case SCMD_WRITE_G1: case SCMD_WRITE_G4: /* G5 READS AND WRITES? */ mfi_start_io(sc, ap, mpd); break; default: mfi_start_scsi(sc, ap, mpd); break; } pkt->pkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD; if (pkt->pkt_flags & FLAG_NOINTR) { if (mfi_poll(sc, ccb, 10000) != DDI_SUCCESS) return (TRAN_FATAL_ERROR); } else mfi_post(sc, ccb); return (TRAN_ACCEPT); } static void mfi_start_scsi(struct mfi_softc *sc, struct scsi_address *ap, struct mfi_pkt_data *mpd) { struct scsi_pkt *pkt = mpd->mpd_pkt; struct mfi_ccb *ccb = mpd->mpd_ccb; struct mfi_cmd_scsi *io; struct mfi_sge32 *sgl; io = (struct mfi_cmd_scsi *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_scsi)); io->cmd = MFI_CMD_LD_SCSI; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->lun = 0; /* always lun 0 */ io->cdb_len = mpd->mpd_cdblen; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); memcpy(io->cdb, pkt->pkt_cdbp, mpd->mpd_cdblen); io->datalen = mfi_load_sgl(mpd, sgl); DPRINTF(MFI_D_HBA, "ccb 0x%08x >> cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); DPRINTF(MFI_D_HBA, " target: %d lun: %d cdb_len: %d nsge: %d", io->target, io->lun, io->cdb_len, io->nsge); DPRINTF(MFI_D_HBA, " context: 0x%08x", io->context); DPRINTF(MFI_D_HBA, " flags: 0x%04x timeout: 0x%04x", io->flags, io->timeout); DPRINTF(MFI_D_HBA, " datalen: %d", io->datalen); DPRINTF(MFI_D_HBA, " sense: 0x%08x %08x", io->sense_lo, io->sense_hi); DPRINTF(MFI_D_HBA, " cdb[0]: 0x%02x", io->cdb[0]); } static void mfi_start_io(struct mfi_softc *sc, struct scsi_address *ap, struct mfi_pkt_data *mpd) { struct scsi_pkt *pkt = mpd->mpd_pkt; struct mfi_ccb *ccb = mpd->mpd_ccb; struct mfi_cmd_io *io; struct mfi_sge32 *sgl; union scsi_cdb *cdb; io = (struct mfi_cmd_io *)ccb->ccb_cmd; sgl = (struct mfi_sge32 *)(ccb->ccb_cmd + sizeof(struct mfi_cmd_io)); io->cmd = mpd->mpd_read ? MFI_CMD_LD_READ : MFI_CMD_LD_WRITE; io->sense_len = MFI_CCB_EXTRALEN; io->target = ap->a_target; io->nsge = mpd->mpd_ncookies; io->context = ccb->ccb_context; io->sense_lo = (uint32_t)ccb->ccb_extra_dva; io->sense_hi = (uint32_t)(ccb->ccb_extra_dva >> 32); cdb = (union scsi_cdb *)pkt->pkt_cdbp; switch (cdb->scc_cmd) { case SCMD_READ: case SCMD_WRITE: io->block_lo = GETG0ADDR(cdb); break; case SCMD_READ_G1: case SCMD_WRITE_G1: io->block_lo = GETG1ADDR(cdb); break; case SCMD_READ_G4: case SCMD_WRITE_G4: io->block_lo = GETG4ADDRTL(cdb); io->block_hi = GETG4ADDR(cdb); break; } io->nblocks = (mfi_load_sgl(mpd, sgl) + 512 - 1) / 512; /* XXX magic */ } static uint32_t mfi_load_sgl(struct mfi_pkt_data *mpd, struct mfi_sge32 *sgl) { int i; uint32_t datalen; for (i = 0; i < mpd->mpd_ncookies; i++) { sgl[i].len = mpd->mpd_cookies.dmac_size; sgl[i].addr = mpd->mpd_cookies.dmac_address; datalen += mpd->mpd_cookies.dmac_size; DPRINTF(MFI_D_HBA, " size: %d addr: 0x%08x", mpd->mpd_cookies.dmac_size, mpd->mpd_cookies.dmac_address); ddi_dma_nextcookie(mpd->mpd_dma_handle, &mpd->mpd_cookies); } return (datalen); } static void mfi_done_tran(void *arg) { struct mfi_ccb *ccb = arg; struct mfi_softc *sc = ccb->ccb_sc; struct mfi_pkt_data *mpd = ccb->ccb_mpd; struct mfi_cmd_hdr *io; struct scsi_pkt *pkt = mpd->mpd_pkt; struct scsi_arq_status *arqstat; io = (struct mfi_cmd_hdr *)ccb->ccb_cmd; DPRINTF(MFI_D_HBA, "ccb 0x%08x << cmd: 0x%02x sense_len: %d " "status: 0x%02x scsi_status: 0x%02x", ccb->ccb_context, io->cmd, io->sense_len, io->status, io->scsi_status); switch (io->status) { case MFI_STAT_SCSI_DONE_WITH_ERROR: pkt->pkt_state |= STATE_GOT_STATUS; pkt->pkt_scbp[0] = io->scsi_status; if (io->scsi_status == STATUS_CHECK) { pkt->pkt_state |= STATE_ARQ_DONE; arqstat = (struct scsi_arq_status *)pkt->pkt_scbp; arqstat->sts_rqpkt_reason = CMD_CMPLT; arqstat->sts_rqpkt_resid = 0; arqstat->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD | STATE_XFERRED_DATA; arqstat->sts_rqpkt_statistics = 0; memcpy(&arqstat->sts_sensedata, ccb->ccb_extra, sizeof(arqstat->sts_sensedata)); } /* FALLTHROUGH */ case MFI_STAT_OK: pkt->pkt_reason = CMD_CMPLT; pkt->pkt_state |= STATE_XFERRED_DATA; pkt->pkt_resid = 0; break; default: cmn_err(CE_NOTE, "%s: %d", __func__, io->status); break; /* XXX deal with other values */ } pkt->pkt_comp(pkt); } static int mfi_tran_reset(struct scsi_address *ap, int level) { #if 0 cmn_err(CE_NOTE, "mfi_tran_reset"); #endif return (0); } static int mfi_tran_getcap(struct scsi_address *ap, char *cap, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: cmn_err(CE_NOTE, "getcap ARQ requested"); return (1); default: cmn_err(CE_NOTE, "getcap %s", cap); break; } return (-1); } static int mfi_tran_setcap(struct scsi_address *ap, char *cap, int value, int whom) { struct mfi_softc *sc; if (cap == NULL || whom == 0) return (-1); sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; switch (scsi_hba_lookup_capstr(cap)) { case SCSI_CAP_ARQ: return (1); #if 0 case SCSI_CAP_TOTAL_SECTORS: return (1); case SCSI_CAP_SECTOR_SIZE: return (1); #endif default: return (0); } return (0); } static struct scsi_pkt * mfi_tran_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags, int (*callback)(caddr_t), caddr_t arg) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; struct scsi_pkt *npkt = NULL; int (*cb)(caddr_t); int dma_flags; int error; int i; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP; DPRINTF(MFI_D_HBA, "init_pkt(%d,%d): pkt: %p buf: %p buflen: %d " "cmdlen: %d statuslen: %d tgtlen: %d flags: 0x%08x", ap->a_target, ap->a_lun, pkt, bp, (bp == NULL) ? 0 : bp->b_bcount, cmdlen, statuslen, tgtlen, flags); /* step 1: packet allocation */ if (pkt == NULL) { if (cmdlen > MFI_CMD_SCSI_CDBLEN || statuslen > MFI_CCB_EXTRALEN) return (NULL); ccb = mfi_get_ccb(sc); if (ccb == NULL) return (NULL); DPRINTF(MFI_D_HBA, "ccb: 0x%08x", ccb->ccb_context); pkt = scsi_hba_pkt_alloc(sc->sc_dev, ap, cmdlen, statuslen, tgtlen, sizeof(struct mfi_pkt_data), callback, arg); if (pkt == NULL) { mfi_put_ccb(sc, ccb); return (NULL); } mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb->ccb_mpd = mpd; mpd->mpd_ccb = ccb; mpd->mpd_pkt = pkt; mpd->mpd_dma = 0; mpd->mpd_cdblen = cmdlen; mpd->mpd_senselen = statuslen; if (ddi_dma_alloc_handle(sc->sc_dev, &sc->sc_io_dma_attr, cb, NULL, &mpd->mpd_dma_handle) != DDI_SUCCESS) { scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); return (NULL); } pkt->pkt_address = *ap; pkt->pkt_comp = (void (*)(struct scsi_pkt *))NULL; pkt->pkt_flags = 0; pkt->pkt_time = 0; pkt->pkt_resid = 0; pkt->pkt_statistics = 0; pkt->pkt_reason = 0; npkt = pkt; } else { mpd = pkt->pkt_ha_private; ccb = mpd->mpd_ccb; } /* step 2: dma allocation */ if (bp == NULL || bp->b_bcount == 0) return (pkt); if (bp->b_flags & B_READ) { dma_flags = DDI_DMA_READ; mpd->mpd_read = 1; } else { dma_flags = DDI_DMA_WRITE; mpd->mpd_read = 0; } if (flags & PKT_CONSISTENT) dma_flags |= DDI_DMA_CONSISTENT; if (flags & PKT_DMA_PARTIAL) dma_flags |= DDI_DMA_PARTIAL; error = ddi_dma_buf_bind_handle(mpd->mpd_dma_handle, bp, dma_flags, cb, NULL, &mpd->mpd_cookies, &mpd->mpd_ncookies); if (error != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "init_pkt: unable to bind handle %d", error); if (npkt != NULL) { ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } return (NULL); } mpd->mpd_dma = 1; return (pkt); } static void mfi_tran_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_ccb *ccb; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; ccb = mpd->mpd_ccb; #if 0 cmn_err(CE_NOTE, "mfi_tran_destroy_pkt"); #endif if (mpd->mpd_dma == 1) ddi_dma_unbind_handle(mpd->mpd_dma_handle); ddi_dma_free_handle(&mpd->mpd_dma_handle); scsi_hba_pkt_free(ap, pkt); mfi_put_ccb(sc, ccb); } static void mfi_tran_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_dmafree"); #endif if (mpd->mpd_dma == 1) { ddi_dma_unbind_handle(mpd->mpd_dma_handle); mpd->mpd_dma = 0; } } static void mfi_tran_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) { struct mfi_softc *sc; struct mfi_pkt_data *mpd; sc = (struct mfi_softc *)ap->a_hba_tran->tran_hba_private; mpd = (struct mfi_pkt_data *)pkt->pkt_ha_private; #if 0 cmn_err(CE_NOTE, "mfi_tran_sync_pkt"); #endif if (mpd->mpd_dma == 0) return; ddi_dma_sync(mpd->mpd_dma_handle, 0, 0, mpd->mpd_read ? DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU); } static int mfi_poll(struct mfi_softc *sc, struct mfi_ccb *ccb, u_int timeout) { struct mfi_cmd_hdr *cmd; cmd = (struct mfi_cmd_hdr *)ccb->ccb_cmd; cmd->status = 0xff; cmd->flags |= MFI_CMD_FLAG_NO_POST_IN_REPLYQ; mfi_post(sc, ccb); /* cmd dma post rw sync */ while (cmd->status == 0xff) { if (timeout-- == 0) { /* XXX this is bad */ return (DDI_FAILURE); } /* cmd dma pre rw sync */ delay(drv_usectohz(1000)); } /* dont add this to a taskq, just run it directly */ ccb->ccb_done(ccb); return (DDI_SUCCESS); } static void mfi_post(struct mfi_softc *sc, struct mfi_ccb *ccb) { /* cmd dma pre rw sync */ mfi_write(sc, MFI_IQP, (ccb->ccb_cmd_dva >> 3) | 0x7); } static void mfi_done(void *arg) { /* empty handler handler */ } static int mfi_alloc_pcq(struct mfi_softc *sc) { uint8_t *kva; size_t len, rlen; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = sizeof(struct mfi_prod_cons) + (sc->sc_ncmds + 1) * sizeof(uint32_t); if (ddi_dma_alloc_handle(sc->sc_dev, &pcq_dma_attr, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for pcq"); goto err; } if (ddi_dma_mem_alloc(sc->sc_pcq_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&kva, &rlen, &sc->sc_pcq_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for pcq"); goto free_dma; } if (ddi_dma_addr_bind_handle(sc->sc_pcq_dma_handle, NULL, (caddr_t)kva, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &sc->sc_pcq_dma_cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } sc->sc_pc = (struct mfi_prod_cons *)kva; sc->sc_pc->consumer = 0x0; sc->sc_pc->producer = 0x0; sc->sc_replyq = (uint32_t *)(kva + sizeof(struct mfi_prod_cons)); memset(sc->sc_replyq, 0xff, (sc->sc_ncmds + 1) * sizeof(uint32_t)); return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); free_mem: ddi_dma_mem_free(&sc->sc_pcq_acc_handle); free_dma: ddi_dma_free_handle(&sc->sc_pcq_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_pcq(struct mfi_softc *sc) { ddi_dma_unbind_handle(sc->sc_pcq_dma_handle); ddi_dma_mem_free(&sc->sc_pcq_acc_handle); ddi_dma_free_handle(&sc->sc_pcq_dma_handle); } static int mfi_alloc_ccbs(struct mfi_softc *sc) { struct mfi_ccb *ccb; int i; sc->sc_ccbs = kmem_alloc(sizeof(struct mfi_ccb) * sc->sc_ncmds, KM_SLEEP); if (sc->sc_ccbs == NULL) { /* is this check needed if we can sleep? */ return (DDI_FAILURE); } TAILQ_INIT(&sc->sc_ccb_list); for (i = 0; i < sc->sc_ncmds; i++) { ccb = &sc->sc_ccbs[i]; ccb->ccb_sc = sc; ccb->ccb_context = i; TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); } return (DDI_SUCCESS); } static void mfi_free_ccbs(struct mfi_softc *sc) { kmem_free(sc->sc_ccbs, sizeof(struct mfi_ccb) * sc->sc_ncmds); } static struct mfi_ccb * mfi_alloc_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; mutex_enter(&sc->sc_ccb_mutex); ccb = TAILQ_FIRST(&sc->sc_ccb_list); if (ccb != NULL) TAILQ_REMOVE(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); return (ccb); } static void mfi_free_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { mutex_enter(&sc->sc_ccb_mutex); TAILQ_INSERT_TAIL(&sc->sc_ccb_list, ccb, ccb_link); mutex_exit(&sc->sc_ccb_mutex); } static struct mfi_ccb * mfi_get_ccb(struct mfi_softc *sc) { struct mfi_ccb *ccb; ccb = mfi_alloc_ccb(sc); if (ccb == NULL) return (NULL); if (mfi_alloc_cmdbuf(sc, ccb) != DDI_SUCCESS) mfi_free_ccb(sc, ccb); DPRINTF(MFI_D_CCB, "mfi_get_ccb: ccb 0x%08x\n", ccb->ccb_context); return (ccb); } static void mfi_put_ccb(struct mfi_softc *sc, struct mfi_ccb *ccb) { DPRINTF(MFI_D_CCB, "mfi_put_ccb: ccb 0x%08x\n", ccb->ccb_context); mfi_free_cmdbuf(sc, ccb); ccb->ccb_mpd = NULL; ccb->ccb_done = NULL; ccb->ccb_extra_frames = 0; mfi_free_ccb(sc, ccb); } static int mfi_alloc_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { size_t len, rlen; ddi_dma_cookie_t cookie; uint_t ncookies; /* stick the producer and consumer indexes on the front of the queue */ len = MFI_CCB_BUFLEN; if (ddi_dma_alloc_handle(sc->sc_dev, &cmd_dma_attr, DDI_DMA_SLEEP, NULL, &ccb->ccb_dma_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma handle for ccb"); goto err; } if (ddi_dma_mem_alloc(ccb->ccb_dma_handle, len, &mfi_acc_attr, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, (caddr_t *)&ccb->ccb_cmd, &rlen, &ccb->ccb_acc_handle) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc dma memory for ccb"); goto free_dma; } if (ddi_dma_addr_bind_handle(ccb->ccb_dma_handle, NULL, (caddr_t)ccb->ccb_cmd, len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &cookie, &ncookies) != DDI_DMA_MAPPED) { cmn_err(CE_NOTE, "unable to bind pcq handle"); goto free_mem; } if (ncookies != 1) { cmn_err(CE_NOTE, "unexpected number of cookies for pcq"); goto unbind_handle; } bzero(ccb->ccb_cmd, MFI_CCB_BUFLEN); ccb->ccb_cmd_dva = cookie.dmac_laddress; ccb->ccb_extra = ccb->ccb_cmd + MFI_CCB_CMDLEN; ccb->ccb_extra_dva = cookie.dmac_laddress + MFI_CCB_CMDLEN; return (DDI_SUCCESS); unbind_handle: ddi_dma_unbind_handle(ccb->ccb_dma_handle); free_mem: ddi_dma_mem_free(&ccb->ccb_acc_handle); free_dma: ddi_dma_free_handle(&ccb->ccb_dma_handle); err: return (DDI_FAILURE); } static void mfi_free_cmdbuf(struct mfi_softc *sc, struct mfi_ccb *ccb) { ddi_dma_unbind_handle(ccb->ccb_dma_handle); ddi_dma_mem_free(&ccb->ccb_acc_handle); ddi_dma_free_handle(&ccb->ccb_dma_handle); } static int mfi_wait_eq(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) == target) return (0); delay(drv_usectohz(100000)); } return (1); } static int mfi_wait_ne(struct mfi_softc *sc, off_t r, uint32_t mask, uint32_t target, int timeout) { int i; for (i = 0; i < timeout * 10; i++) { if ((mfi_read(sc, r) & mask) != target) return (0); delay(drv_usectohz(100000)); } return (1); } Revision-number: 61 Prop-content-length: 138 Content-length: 138 K 7 svn:log V 35 wrap command submission in a mutex K 10 svn:author V 8 dlg@itee K 8 svn:date V 27 2006-11-21T09:39:11.159660Z PROPS-END Node-path: mfi.c Node-kind: file Node-action: change Text-content-length: 33984 Text-content-md5: 33711cc99d93f1bc285a39cfd59411cc Content-length: 33984 /* $Id$ */ /* * Copyright (c) 2006 The University of Queensland * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This code was written by David Gwynne as part of the * IT Infrastructure Group in the School of Information Technology and * Electrical Engineering. */ #include #include #include #include #include #include #include #include "mfireg.h" /* i need queues, and i cant be bothered finding some in solaris */ #include "queue.h" #if 0 #define MFI_DEBUG #endif #ifdef MFI_DEBUG #define MFI_D_CCB (1<<0) #define MFI_D_HBA (1<<1) #define DPRINTF(mask, fmt, ...) \ do { \ if ((mask) & mfidebug) \ cmn_err(CE_NOTE, fmt, __VA_ARGS__); \ } while (0) static int mfidebug = MFI_D_HBA; #else #define DPRINTF(m, v...) #endif char _depends_on[] = "misc/scsi"; static int mfi_attach(dev_info_t *, ddi_attach_cmd_t); static int mfi_detach(dev_info_t *, ddi_attach_cmd_t); static struct dev_ops mfi_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ nodev, /* info */ nulldev, /* identify */ nulldev, /* probe */ mfi_attach, /* attach */ mfi_detach, /* detach */ nodev, /* reset */ NULL, /* driver ops */ NULL, /* bus ops */ 0 /* power */ }; static void *mfi_softc_p; static struct modldrv md = { &mod_driverops, "LSI Logic MegaRAID SAS", &mfi_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL }; struct mfi_softc; struct mfi_pkt_data; struct mfi_ccb { struct mfi_softc *ccb_sc; struct mfi_pkt_data *ccb_mpd; void (*ccb_done)(void *); uint32_t ccb_context; ddi_acc_handle_t ccb_acc_handle; ddi_dma_handle_t ccb_dma_handle; #define MFI_CCB_CMDLEN (MFI_FRAME_SIZE * MFI_FRAME_COUNT) #define MFI_CCB_EXTRALEN 32 /* mostly for sense data */ #define MFI_CCB_BUFLEN (MFI_CCB_CMDLEN + MFI_CCB_EXTRALEN) uint8_t *ccb_cmd; uint64_t ccb_cmd_dva; uint8_t *ccb_extra; uint64_t ccb_extra_dva; int ccb_extra_frames; TAILQ_ENTRY(mfi_ccb) ccb_link; }; struct mfi_pkt_data { struct mfi_ccb *mpd_ccb; struct scsi_pkt *mpd_pkt; ddi_dma_handle_t mpd_dma_handle; ddi_dma_cookie_t mpd_cookies; uint_t mpd_ncookies; int mpd_dma; int mpd_read; int mpd_cdblen; int mpd_senselen; }; struct mfi_softc { dev_info_t *sc_dev; scsi_hba_tran_t *sc_tran; int sc_ncmds; ddi_acc_handle_t sc_reg_space; char *sc_reg_baseaddr; ddi_iblock_cookie_t sc_iblock_cookie; kmutex_t sc_post_mutex; kmutex_t sc_replyq_mutex; ddi_taskq_t *sc_taskq; ddi_acc_handle_t sc_pcq_acc_handle; ddi_dma_handle_t sc_pcq_dma_handle; ddi_dma_cookie_t sc_pcq_dma_cookie; struct mfi_prod_cons *sc_pc; uint32_t *sc_replyq; ddi_dma_attr_t sc_io_dma_attr; struct mfi_ccb *sc_ccbs; kmutex_t sc_ccb_mutex; TAILQ_HEAD(, mfi_ccb) sc_ccb_list; }; static uint_t mfi_intr(caddr_t); #define mfi_read(_s, _r) ddi_get32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r))) #define mfi_write(_s, _r, _v) ddi_put32((_s)->sc_reg_space, \ (uint32_t *)((_s)->sc_reg_baseaddr + (_r)), (_v)) static int mfi_wait_eq(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_wait_ne(struct mfi_softc *, off_t, uint32_t, uint32_t, int); static int mfi_pci_work(dev_info_t *); static int mfi_transition_firmware(struct mfi_softc *); static int mfi_init_firmware(struct mfi_softc *); static int mfi_alloc_pcq(struct mfi_softc *); static void mfi_free_pcq(struct mfi_softc *); static int mfi_alloc_ccbs(struct mfi_softc *); static void mfi_free_ccbs(struct mfi_softc *); static struct mfi_ccb *mfi_alloc_ccb(struct mfi_softc *); static void mfi_free_ccb(struct mfi_softc *, struct mfi_ccb *); static struct mfi_ccb *mfi_get_ccb(struct mfi_softc *); static void mfi_put_ccb(struct mfi_softc *, struct mfi_ccb *); static int mfi_alloc_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_free_cmdbuf(struct mfi_softc *, struct mfi_ccb *); static void mfi_post(struct mfi_softc *, struct mfi_ccb *); static int mfi_poll(struct mfi_softc *, struct mfi_ccb *, u_int); static void mfi_done(void *); static uint32_t mfi_load_sgl(struct mfi_pkt_data *, struct mfi_sge32 *); static void mfi_start_io(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_start_scsi(struct mfi_softc *, struct scsi_address *, struct mfi_pkt_data *); static void mfi_done_tran(void *); static int mfi_hba_attach(struct mfi_softc *); static void mfi_hba_detach(struct mfi_softc *); static int mfi_tran_tgt_init(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, struct scsi_device *); static int mfi_tran_start(struct scsi_address *, struct scsi_pkt *); static int mfi_tran_reset(struct scsi_address *, int); static int mfi_tran_getcap(struct scsi_address *, char *, int); static int mfi_tran_setcap(struct scsi_address *, char *, int, int); static struct scsi_pkt *mfi_tran_init_pkt(struct scsi_address *, struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); static void mfi_tran_destroy_pkt(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_dmafree(struct scsi_address *, struct scsi_pkt *); static void mfi_tran_sync_pkt(struct scsi_address *, struct scsi_pkt *); static ddi_dma_attr_t pcq_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 4, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t cmd_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 64, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 1, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; static ddi_dma_attr_t io_dma_attr = { DMA_ATTR_V0, /* version of this structure */ 0, /* lowest usable address */ 0xffffffffull, /* highest usable address */ 0x7fffffff, /* maximum DMAable byte count */ 1, /* alignment */ 1, /* burst sizes */ 1, /* minimum transfer */ 0xffffffffull, /* maximum transfer */ 0xffffffffull, /* maximum segment length */ 50, /* maximum number of segments */ 1, /* granularity */ 0 /* flags (reserved) */ }; int _init(void) { int error; error = ddi_soft_state_init(&mfi_softc_p, sizeof(struct mfi_softc), 1); if (error != 0) goto err; error = scsi_hba_init(&ml); if (error != 0) goto state_fini; error = mod_install(&ml); if (error != 0) goto hba_fini; return (error); hba_fini: scsi_hba_fini(&ml); state_fini: ddi_soft_state_fini(mfi_softc_p); err: return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&ml, modinfop)); } int _fini(void) { int error; error = mod_remove(&ml); if (error) return (error); scsi_hba_fini(&ml); ddi_soft_state_fini(&mfi_softc_p); return (error); } /* how to access the register space */ static ddi_device_acc_attr_t mfi_acc_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static int mfi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; uint32_t status; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(mfi_softc_p, instance) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to alloc softc"); goto err; } sc = ddi_get_soft_state(mfi_softc_p, instance); sc->sc_dev = dip; sc->sc_io_dma_attr = io_dma_attr; if (mfi_pci_work(dip) != DDI_SUCCESS) { /* error printed by mfi_pci_work */ goto free_sc; } if (ddi_regs_map_setup(dip, 1, (caddr_t *)&sc->sc_reg_baseaddr, 0, 0, &mfi_acc_attr, &sc->sc_reg_space) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map register space"); goto free_sc; } /* hook up interrupt */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_NOTE, "high level interrupt is not supported"); goto free_sc; } if (ddi_get_iblock_cookie(dip, 0, &sc->sc_iblock_cookie) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to get iblock cookie"); goto free_sc; } mutex_init(&sc->sc_post_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_replyq_mutex, NULL, MUTEX_DRIVER, sc->sc_iblock_cookie); mutex_init(&sc->sc_ccb_mutex, NULL, MUTEX_DRIVER, NULL); sc->sc_taskq = ddi_taskq_create(dip, "mfi_taskq", 4, TASKQ_DEFAULTPRI, 0); if (sc->sc_taskq == NULL) { cmn_err(CE_NOTE, "unable to create task queue"); goto unmutex; } if (mfi_transition_firmware(sc) != DDI_SUCCESS) { /* error printed by mfi_transition_firmware */ goto untaskq; } status = mfi_read(sc, MFI_OMSG0); sc->sc_ncmds = MFI_OMSG0_MAXCMD(status); /* the hardware may support more or less than we can fit */ sc->sc_io_dma_attr.dma_attr_sgllen = MIN(MFI_MAX_SGL_LEN, MFI_OMSG0_MAXSGL(status)); if (mfi_alloc_pcq(sc) != DDI_SUCCESS) { /* error printed by mfi_alloc_replyq */ goto untaskq; } if (mfi_alloc_ccbs(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to allocate ccbs"); goto free_pcq; } if (mfi_init_firmware(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to initialise firmware"); goto free_ccbs; } if (ddi_add_intr(sc->sc_dev, 0, &sc->sc_iblock_cookie, NULL, mfi_intr, (caddr_t)sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to establish interrupt"); goto free_ccbs; } mfi_write(sc, MFI_OMSK, MFI_ENABLE_INTR); if (mfi_hba_attach(sc) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to attach scsi"); goto del_intr; } return (DDI_SUCCESS); del_intr: ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); free_ccbs: mfi_free_ccbs(sc); free_pcq: mfi_free_pcq(sc); untaskq: ddi_taskq_destroy(sc->sc_taskq); unmutex: mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_post_mutex); free_sc: ddi_soft_state_free(mfi_softc_p, instance); err: return (DDI_FAILURE); } static int mfi_detach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct mfi_softc *sc; int instance; switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: default: return (DDI_FAILURE); } instance = ddi_get_instance(dip); sc = ddi_get_soft_state(mfi_softc_p, instance); mfi_hba_detach(sc); ddi_remove_intr(sc->sc_dev, 0, sc->sc_iblock_cookie); mfi_free_ccbs(sc); mfi_free_pcq(sc); ddi_taskq_destroy(sc->sc_taskq); mutex_destroy(&sc->sc_replyq_mutex); mutex_destroy(&sc->sc_post_mutex); ddi_regs_map_free(&sc->sc_reg_space); ddi_soft_state_free(mfi_softc_p, instance); return (DDI_SUCCESS); } static uint_t mfi_intr(caddr_t arg) { struct mfi_softc *sc = (struct mfi_softc *)arg; struct mfi_ccb *ccb; uint32_t status, producer, consumer, context; uint_t rv = DDI_INTR_UNCLAIMED; if (mutex_tryenter(&sc->sc_replyq_mutex) == 0) return (DDI_INTR_UNCLAIMED); status = mfi_read(sc, MFI_OSTS); if ((status & MFI_OSTS_INTR_VALID) == 0) { mutex_exit(&sc->sc_replyq_mutex); return (DDI_INTR_UNCLAIMED); } producer = sc->sc_pc->producer; consumer = sc->sc_pc->consumer; while (consumer != producer) { context = sc->sc_replyq[consumer]; /* XXX check consumer is < ncmds */ if (context != 0xffffffff) { ccb = &sc->sc_ccbs[context]; if (ddi_taskq_dispatch(sc->sc_taskq, ccb->ccb_done, ccb, DDI_NOSLEEP) != DDI_SUCCESS) break; sc->sc_replyq[consumer] = 0xffffffff; } rv = DDI_INTR_CLAIMED; consumer++; consumer %= sc->sc_ncmds + 1; } sc->sc_pc->consumer = consumer; if (consumer == producer) mfi_write(sc, MFI_OSTS, status); mutex_exit(&sc->sc_replyq_mutex); return (rv); } static int mfi_pci_work(dev_info_t *dip) { ddi_acc_handle_t pci_conf; uint16_t command; int rv = DDI_SUCCESS; if (pci_config_setup(dip, &pci_conf) != DDI_SUCCESS) { cmn_err(CE_NOTE, "unable to map pci config space"); return (DDI_FAILURE); } /* force the busmaster enable bit on */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { command |= PCI_COMM_ME; pci_config_put16(pci_conf, PCI_CONF_COMM, command); /* check if it is enabled */ command = pci_config_get16(pci_conf, PCI_CONF_COMM); if ((command & PCI_COMM_ME) == 0) { cmn_err(CE_NOTE, "unable to enable bus mastering"); rv = DDI_FAILURE; } } pci_config_teardown(&pci_conf); return (rv); } static int mfi_transition_firmware(struct mfi_softc *sc) { uint32_t state; int wait; for (;;) { state = mfi_read(sc, MFI_OMSG0) & MFI_OMSG0_FWSTATE; switch (state) { case MFI_FWSTATE_READY: return (DDI_SUCCESS); case MFI_FWSTATE_FAULT: cmn_err(CE_NOTE, "firmware fault"); return (DDI_FAILURE); case MFI_FWSTATE_WAIT_HANDSHAKE: mfi_write(sc, MFI_IDB, MFI_IDB_CLEAR_HANDSHAKE); case MFI_FWSTATE_UNDEFINED: case MFI_FWSTATE_BB_INIT: wait = 2; break; case MFI_FWSTATE_OPERATIONAL: mfi_write(sc, MFI_IDB, MFI_IDB_INIT_READY); wait = 10; break; case MFI_FWSTATE_FW_INIT: case MFI_FWSTATE_DEVICE_SCAN: case MFI_FWSTATE_FLUSH_CACHE: wait = 20; break; default: cmn_err(CE_NOTE, "unknown firmware state"); return (DDI_FAILURE); } if (mfi_wait_ne(sc, MFI_OMSG0, MFI_OMSG0_FWSTATE, state, wait) != 0) { cmn_err(CE_NOTE, "firmware wont transition"); return (DDI_FAILURE); } } } static int mfi_init_firmware(struct mfi_softc *sc) { struct mfi_ccb *ccb; struct mfi_cmd_fwinit *init; struct mfi_fwinit_qinfo *qinfo; uint64_t dva; ccb = mfi_get_ccb(sc); if (ccb == NULL) return (DDI_FAILURE); ccb->ccb_done = mfi_done; init = (struct mfi_cmd_fwinit *)ccb->ccb_cmd; init->cmd = MFI_CMD_FWINIT; init->context = LE_32(ccb->ccb_context); dva = ccb->ccb_cmd_dva + MFI_FRAME_SIZE; init->qinfo_new_lo = LE_32(dva); init->qinfo_new_hi = LE_32(dva >> 32); /* * we can use some of the spare message frames in the ccb rather than * allocate more dma memory. */ qinfo = (struct mfi_fwinit_qinfo *)(ccb->ccb_cmd + MFI_FRAME_SIZE); // qinfo->init_flags = MFI_FWINIT_PTRS64; qinfo->q_entries = LE_32(sc->sc_ncmds + 1); dva = sc->sc_pcq_dma_cookie.dmac_laddress; qinfo->prod_lo = LE_32(dva); qinfo->prod_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->cons_lo = LE_32(dva); qinfo->cons_hi = LE_32(dva >> 32); dva += sizeof(uint32_t); qinfo->q_addr_lo = LE_32(dva); qinfo->q_addr_hi = LE_32(dva >> 32); mfi_poll(sc, ccb, 10000); cmn_err(CE_NOTE, "omg initted"); mfi_put_ccb(sc, ccb); return (DDI_SUCCESS); } static int mfi_hba_attach(struct mfi_softc *sc) { scsi_hba_tran_t *tran; tran = scsi_hba_tran_alloc(sc->sc_dev, SCSI_HBA_CANSLEEP); if (tran == NULL) return (DDI_FAILURE); tran->tran_hba_private = sc; tran->tran_tgt_private = NULL; tran->tran_tgt_init = mfi_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; /* tran->tran_tgt_free */ tran->tran_start = mfi_tran_start; tran->tran_reset = mfi_tran_reset; tran->tran_getcap = mfi_tran_getcap; tran->tran_setcap = mfi_tran_setcap; tran->tran_init_pkt = mfi_tran_init_pkt; tran->tran_destroy_pkt = mfi_tran_destroy_pkt; tran->tran_dmafree = mfi_tran_dmafree; tran->tran_sync_pkt = mfi_tran_sync_pkt; tran->tran_abort = NULL; tran->tran_tgt_free = NULL; tran->tran_quiesce = NULL; tran->tran_unquiesce = NULL; tran->tran_sd = NULL; if (scsi_hba_attach_setup(sc->sc_dev, &io_dma_attr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) goto tran_free; sc->sc_tran = tran; return (DDI_SUCCESS); tran_free: scsi_hba_tran_free(tran); err: return (DDI_FAILURE); } static void mfi_hba_detach(struct mfi_softc *sc) { scsi_hba_detach(sc->sc_dev); scsi_hba_tran_free(sc->sc_tran); }