1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright (c) 2017, Joyent, Inc.
14 */
15
16 #include <sys/scsi/adapters/smrt/smrt.h>
17
18 /*
19 * We must locate what the CISS specification describes as the "I2O
20 * registers". The Intelligent I/O (I2O) Architecture Specification describes
21 * this somewhat more coherently as "the memory region specified by the first
22 * base address configuration register indicating memory space (offset 10h,
23 * 14h, and so forth)".
24 */
25 static int
smrt_locate_bar(pci_regspec_t * regs,unsigned nregs,unsigned * i2o_bar)26 smrt_locate_bar(pci_regspec_t *regs, unsigned nregs,
27 unsigned *i2o_bar)
28 {
29 /*
30 * Locate the first memory-mapped BAR:
31 */
32 for (unsigned i = 0; i < nregs; i++) {
33 unsigned type = regs[i].pci_phys_hi & PCI_ADDR_MASK;
34
35 if (type == PCI_ADDR_MEM32 || type == PCI_ADDR_MEM64) {
36 *i2o_bar = i;
37 return (DDI_SUCCESS);
38 }
39 }
40
41 return (DDI_FAILURE);
42 }
43
44 static int
smrt_locate_cfgtbl(smrt_t * smrt,pci_regspec_t * regs,unsigned nregs,unsigned * ct_bar,uint32_t * baseaddr)45 smrt_locate_cfgtbl(smrt_t *smrt, pci_regspec_t *regs, unsigned nregs,
46 unsigned *ct_bar, uint32_t *baseaddr)
47 {
48 uint32_t cfg_offset, mem_offset;
49 unsigned want_type;
50 uint32_t want_bar;
51
52 cfg_offset = smrt_get32(smrt, CISS_I2O_CFGTBL_CFG_OFFSET);
53 mem_offset = smrt_get32(smrt, CISS_I2O_CFGTBL_MEM_OFFSET);
54
55 VERIFY3U(cfg_offset, !=, 0xffffffff);
56 VERIFY3U(mem_offset, !=, 0xffffffff);
57
58 /*
59 * Locate the Configuration Table. Three different values read
60 * from two I2O registers allow us to determine the location:
61 * - the correct PCI BAR offset is in the low 16 bits of
62 * CISS_I2O_CFGTBL_CFG_OFFSET
63 * - bit 16 is 0 for a 32-bit space, and 1 for 64-bit
64 * - the memory offset from the base of this BAR is
65 * in CISS_I2O_CFGTBL_MEM_OFFSET
66 */
67 want_bar = (cfg_offset & 0xffff);
68 want_type = (cfg_offset & (1UL << 16)) ? PCI_ADDR_MEM64 :
69 PCI_ADDR_MEM32;
70
71 DTRACE_PROBE4(locate_cfgtbl, uint32_t, want_bar, unsigned,
72 want_type, uint32_t, cfg_offset, uint32_t, mem_offset);
73
74 for (unsigned i = 0; i < nregs; i++) {
75 unsigned type = regs[i].pci_phys_hi & PCI_ADDR_MASK;
76 unsigned bar = PCI_REG_REG_G(regs[i].pci_phys_hi);
77
78 if (type != PCI_ADDR_MEM32 && type != PCI_ADDR_MEM64) {
79 continue;
80 }
81
82 if (bar == want_bar) {
83 *ct_bar = i;
84 *baseaddr = mem_offset;
85 return (DDI_SUCCESS);
86 }
87 }
88
89 return (DDI_FAILURE);
90 }
91
92 /*
93 * Determine the PCI vendor and device ID which is a proxy for which generation
94 * of controller we're working with.
95 */
96 static int
smrt_identify_device(smrt_t * smrt)97 smrt_identify_device(smrt_t *smrt)
98 {
99 ddi_acc_handle_t pci_hdl;
100
101 if (pci_config_setup(smrt->smrt_dip, &pci_hdl) != DDI_SUCCESS)
102 return (DDI_FAILURE);
103
104 smrt->smrt_pci_vendor = pci_config_get16(pci_hdl, PCI_CONF_VENID);
105 smrt->smrt_pci_device = pci_config_get16(pci_hdl, PCI_CONF_DEVID);
106
107 pci_config_teardown(&pci_hdl);
108
109 return (DDI_SUCCESS);
110 }
111
112 static int
smrt_map_device(smrt_t * smrt)113 smrt_map_device(smrt_t *smrt)
114 {
115 pci_regspec_t *regs;
116 uint_t regslen, nregs;
117 dev_info_t *dip = smrt->smrt_dip;
118 int r = DDI_FAILURE;
119
120 /*
121 * Get the list of PCI registers from the DDI property "regs":
122 */
123 if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
124 "reg", (int **)®s, ®slen) != DDI_PROP_SUCCESS) {
125 dev_err(dip, CE_WARN, "could not load \"reg\" DDI prop");
126 return (DDI_FAILURE);
127 }
128 nregs = regslen * sizeof (int) / sizeof (pci_regspec_t);
129
130 if (smrt_locate_bar(regs, nregs, &smrt->smrt_i2o_bar) !=
131 DDI_SUCCESS) {
132 dev_err(dip, CE_WARN, "did not find any memory BARs");
133 goto out;
134 }
135
136 /*
137 * Map enough of the I2O memory space to enable us to talk to the
138 * device.
139 */
140 if (ddi_regs_map_setup(dip, smrt->smrt_i2o_bar, &smrt->smrt_i2o_space,
141 CISS_I2O_MAP_BASE, CISS_I2O_MAP_LIMIT - CISS_I2O_MAP_BASE,
142 &smrt_dev_attributes, &smrt->smrt_i2o_handle) != DDI_SUCCESS) {
143 dev_err(dip, CE_WARN, "failed to map I2O registers");
144 goto out;
145 }
146 smrt->smrt_init_level |= SMRT_INITLEVEL_I2O_MAPPED;
147
148 if (smrt_locate_cfgtbl(smrt, regs, nregs, &smrt->smrt_ct_bar,
149 &smrt->smrt_ct_baseaddr) != DDI_SUCCESS) {
150 dev_err(dip, CE_WARN, "could not find config table");
151 goto out;
152 }
153
154 /*
155 * Map the Configuration Table.
156 */
157 if (ddi_regs_map_setup(dip, smrt->smrt_ct_bar,
158 (caddr_t *)&smrt->smrt_ct, smrt->smrt_ct_baseaddr,
159 sizeof (CfgTable_t), &smrt_dev_attributes,
160 &smrt->smrt_ct_handle) != DDI_SUCCESS) {
161 dev_err(dip, CE_WARN, "could not map config table");
162 goto out;
163 }
164 smrt->smrt_init_level |= SMRT_INITLEVEL_CFGTBL_MAPPED;
165
166 r = DDI_SUCCESS;
167
168 out:
169 ddi_prop_free(regs);
170 return (r);
171 }
172
173 int
smrt_device_setup(smrt_t * smrt)174 smrt_device_setup(smrt_t *smrt)
175 {
176 /*
177 * Ensure that the controller is installed in such a fashion that it
178 * may become a DMA master.
179 */
180 if (ddi_slaveonly(smrt->smrt_dip) == DDI_SUCCESS) {
181 dev_err(smrt->smrt_dip, CE_WARN, "device cannot become DMA "
182 "master");
183 return (DDI_FAILURE);
184 }
185
186 if (smrt_identify_device(smrt) != DDI_SUCCESS)
187 goto fail;
188
189 if (smrt_map_device(smrt) != DDI_SUCCESS) {
190 goto fail;
191 }
192
193 return (DDI_SUCCESS);
194
195 fail:
196 smrt_device_teardown(smrt);
197 return (DDI_FAILURE);
198 }
199
200 void
smrt_device_teardown(smrt_t * smrt)201 smrt_device_teardown(smrt_t *smrt)
202 {
203 if (smrt->smrt_init_level & SMRT_INITLEVEL_CFGTBL_MAPPED) {
204 ddi_regs_map_free(&smrt->smrt_ct_handle);
205 smrt->smrt_init_level &= ~SMRT_INITLEVEL_CFGTBL_MAPPED;
206 }
207
208 if (smrt->smrt_init_level & SMRT_INITLEVEL_I2O_MAPPED) {
209 ddi_regs_map_free(&smrt->smrt_i2o_handle);
210 smrt->smrt_init_level &= ~SMRT_INITLEVEL_I2O_MAPPED;
211 }
212 }
213
214 uint32_t
smrt_get32(smrt_t * smrt,offset_t off)215 smrt_get32(smrt_t *smrt, offset_t off)
216 {
217 VERIFY3S(off, >=, CISS_I2O_MAP_BASE);
218 VERIFY3S(off, <, CISS_I2O_MAP_BASE + CISS_I2O_MAP_LIMIT);
219
220 /* LINTED: E_BAD_PTR_CAST_ALIGN */
221 uint32_t *addr = (uint32_t *)(smrt->smrt_i2o_space +
222 (off - CISS_I2O_MAP_BASE));
223
224 return (ddi_get32(smrt->smrt_i2o_handle, addr));
225 }
226
227 void
smrt_put32(smrt_t * smrt,offset_t off,uint32_t val)228 smrt_put32(smrt_t *smrt, offset_t off, uint32_t val)
229 {
230 VERIFY3S(off, >=, CISS_I2O_MAP_BASE);
231 VERIFY3S(off, <, CISS_I2O_MAP_BASE + CISS_I2O_MAP_LIMIT);
232
233 /* LINTED: E_BAD_PTR_CAST_ALIGN */
234 uint32_t *addr = (uint32_t *)(smrt->smrt_i2o_space +
235 (off - CISS_I2O_MAP_BASE));
236
237 ddi_put32(smrt->smrt_i2o_handle, addr, val);
238 }
239