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 **)&regs, &regslen) != 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