diff options
Diffstat (limited to 'drivers/scsi/aic94xx/aic94xx_dev.c')
-rw-r--r-- | drivers/scsi/aic94xx/aic94xx_dev.c | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/drivers/scsi/aic94xx/aic94xx_dev.c b/drivers/scsi/aic94xx/aic94xx_dev.c new file mode 100644 index 000000000000..6f8901b748f7 --- /dev/null +++ b/drivers/scsi/aic94xx/aic94xx_dev.c | |||
@@ -0,0 +1,353 @@ | |||
1 | /* | ||
2 | * Aic94xx SAS/SATA DDB management | ||
3 | * | ||
4 | * Copyright (C) 2005 Adaptec, Inc. All rights reserved. | ||
5 | * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com> | ||
6 | * | ||
7 | * This file is licensed under GPLv2. | ||
8 | * | ||
9 | * This file is part of the aic94xx driver. | ||
10 | * | ||
11 | * The aic94xx driver is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License as | ||
13 | * published by the Free Software Foundation; version 2 of the | ||
14 | * License. | ||
15 | * | ||
16 | * The aic94xx driver is distributed in the hope that it will be useful, | ||
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
19 | * General Public License for more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License | ||
22 | * along with the aic94xx driver; if not, write to the Free Software | ||
23 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
24 | * | ||
25 | * $Id: //depot/aic94xx/aic94xx_dev.c#21 $ | ||
26 | */ | ||
27 | |||
28 | #include "aic94xx.h" | ||
29 | #include "aic94xx_hwi.h" | ||
30 | #include "aic94xx_reg.h" | ||
31 | #include "aic94xx_sas.h" | ||
32 | |||
33 | #define FIND_FREE_DDB(_ha) find_first_zero_bit((_ha)->hw_prof.ddb_bitmap, \ | ||
34 | (_ha)->hw_prof.max_ddbs) | ||
35 | #define SET_DDB(_ddb, _ha) set_bit(_ddb, (_ha)->hw_prof.ddb_bitmap) | ||
36 | #define CLEAR_DDB(_ddb, _ha) clear_bit(_ddb, (_ha)->hw_prof.ddb_bitmap) | ||
37 | |||
38 | static inline int asd_get_ddb(struct asd_ha_struct *asd_ha) | ||
39 | { | ||
40 | unsigned long flags; | ||
41 | int ddb, i; | ||
42 | |||
43 | spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); | ||
44 | ddb = FIND_FREE_DDB(asd_ha); | ||
45 | if (ddb >= asd_ha->hw_prof.max_ddbs) { | ||
46 | ddb = -ENOMEM; | ||
47 | spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); | ||
48 | goto out; | ||
49 | } | ||
50 | SET_DDB(ddb, asd_ha); | ||
51 | spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); | ||
52 | |||
53 | for (i = 0; i < sizeof(struct asd_ddb_ssp_smp_target_port); i+= 4) | ||
54 | asd_ddbsite_write_dword(asd_ha, ddb, i, 0); | ||
55 | out: | ||
56 | return ddb; | ||
57 | } | ||
58 | |||
59 | #define INIT_CONN_TAG offsetof(struct asd_ddb_ssp_smp_target_port, init_conn_tag) | ||
60 | #define DEST_SAS_ADDR offsetof(struct asd_ddb_ssp_smp_target_port, dest_sas_addr) | ||
61 | #define SEND_QUEUE_HEAD offsetof(struct asd_ddb_ssp_smp_target_port, send_queue_head) | ||
62 | #define DDB_TYPE offsetof(struct asd_ddb_ssp_smp_target_port, ddb_type) | ||
63 | #define CONN_MASK offsetof(struct asd_ddb_ssp_smp_target_port, conn_mask) | ||
64 | #define DDB_TARG_FLAGS offsetof(struct asd_ddb_ssp_smp_target_port, flags) | ||
65 | #define DDB_TARG_FLAGS2 offsetof(struct asd_ddb_stp_sata_target_port, flags2) | ||
66 | #define EXEC_QUEUE_TAIL offsetof(struct asd_ddb_ssp_smp_target_port, exec_queue_tail) | ||
67 | #define SEND_QUEUE_TAIL offsetof(struct asd_ddb_ssp_smp_target_port, send_queue_tail) | ||
68 | #define SISTER_DDB offsetof(struct asd_ddb_ssp_smp_target_port, sister_ddb) | ||
69 | #define MAX_CCONN offsetof(struct asd_ddb_ssp_smp_target_port, max_concurrent_conn) | ||
70 | #define NUM_CTX offsetof(struct asd_ddb_ssp_smp_target_port, num_contexts) | ||
71 | #define ATA_CMD_SCBPTR offsetof(struct asd_ddb_stp_sata_target_port, ata_cmd_scbptr) | ||
72 | #define SATA_TAG_ALLOC_MASK offsetof(struct asd_ddb_stp_sata_target_port, sata_tag_alloc_mask) | ||
73 | #define NUM_SATA_TAGS offsetof(struct asd_ddb_stp_sata_target_port, num_sata_tags) | ||
74 | #define SATA_STATUS offsetof(struct asd_ddb_stp_sata_target_port, sata_status) | ||
75 | #define NCQ_DATA_SCB_PTR offsetof(struct asd_ddb_stp_sata_target_port, ncq_data_scb_ptr) | ||
76 | #define ITNL_TIMEOUT offsetof(struct asd_ddb_ssp_smp_target_port, itnl_timeout) | ||
77 | |||
78 | static inline void asd_free_ddb(struct asd_ha_struct *asd_ha, int ddb) | ||
79 | { | ||
80 | unsigned long flags; | ||
81 | |||
82 | if (!ddb || ddb >= 0xFFFF) | ||
83 | return; | ||
84 | asd_ddbsite_write_byte(asd_ha, ddb, DDB_TYPE, DDB_TYPE_UNUSED); | ||
85 | spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags); | ||
86 | CLEAR_DDB(ddb, asd_ha); | ||
87 | spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags); | ||
88 | } | ||
89 | |||
90 | static inline void asd_set_ddb_type(struct domain_device *dev) | ||
91 | { | ||
92 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
93 | int ddb = (int) (unsigned long) dev->lldd_dev; | ||
94 | |||
95 | if (dev->dev_type == SATA_PM_PORT) | ||
96 | asd_ddbsite_write_byte(asd_ha,ddb, DDB_TYPE, DDB_TYPE_PM_PORT); | ||
97 | else if (dev->tproto) | ||
98 | asd_ddbsite_write_byte(asd_ha,ddb, DDB_TYPE, DDB_TYPE_TARGET); | ||
99 | else | ||
100 | asd_ddbsite_write_byte(asd_ha,ddb,DDB_TYPE,DDB_TYPE_INITIATOR); | ||
101 | } | ||
102 | |||
103 | static int asd_init_sata_tag_ddb(struct domain_device *dev) | ||
104 | { | ||
105 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
106 | int ddb, i; | ||
107 | |||
108 | ddb = asd_get_ddb(asd_ha); | ||
109 | if (ddb < 0) | ||
110 | return ddb; | ||
111 | |||
112 | for (i = 0; i < sizeof(struct asd_ddb_sata_tag); i += 2) | ||
113 | asd_ddbsite_write_word(asd_ha, ddb, i, 0xFFFF); | ||
114 | |||
115 | asd_ddbsite_write_word(asd_ha, (int) (unsigned long) dev->lldd_dev, | ||
116 | SISTER_DDB, ddb); | ||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static inline int asd_init_sata(struct domain_device *dev) | ||
121 | { | ||
122 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
123 | int ddb = (int) (unsigned long) dev->lldd_dev; | ||
124 | u32 qdepth = 0; | ||
125 | int res = 0; | ||
126 | |||
127 | asd_ddbsite_write_word(asd_ha, ddb, ATA_CMD_SCBPTR, 0xFFFF); | ||
128 | if ((dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT) && | ||
129 | dev->sata_dev.identify_device && | ||
130 | dev->sata_dev.identify_device[10] != 0) { | ||
131 | u16 w75 = le16_to_cpu(dev->sata_dev.identify_device[75]); | ||
132 | u16 w76 = le16_to_cpu(dev->sata_dev.identify_device[76]); | ||
133 | |||
134 | if (w76 & 0x100) /* NCQ? */ | ||
135 | qdepth = (w75 & 0x1F) + 1; | ||
136 | asd_ddbsite_write_dword(asd_ha, ddb, SATA_TAG_ALLOC_MASK, | ||
137 | (1<<qdepth)-1); | ||
138 | asd_ddbsite_write_byte(asd_ha, ddb, NUM_SATA_TAGS, qdepth); | ||
139 | } | ||
140 | if (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM || | ||
141 | dev->dev_type == SATA_PM_PORT) { | ||
142 | struct dev_to_host_fis *fis = (struct dev_to_host_fis *) | ||
143 | dev->frame_rcvd; | ||
144 | asd_ddbsite_write_byte(asd_ha, ddb, SATA_STATUS, fis->status); | ||
145 | } | ||
146 | asd_ddbsite_write_word(asd_ha, ddb, NCQ_DATA_SCB_PTR, 0xFFFF); | ||
147 | if (qdepth > 0) | ||
148 | res = asd_init_sata_tag_ddb(dev); | ||
149 | return res; | ||
150 | } | ||
151 | |||
152 | static int asd_init_target_ddb(struct domain_device *dev) | ||
153 | { | ||
154 | int ddb, i; | ||
155 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
156 | u8 flags = 0; | ||
157 | |||
158 | ddb = asd_get_ddb(asd_ha); | ||
159 | if (ddb < 0) | ||
160 | return ddb; | ||
161 | |||
162 | dev->lldd_dev = (void *) (unsigned long) ddb; | ||
163 | |||
164 | asd_ddbsite_write_byte(asd_ha, ddb, 0, DDB_TP_CONN_TYPE); | ||
165 | asd_ddbsite_write_byte(asd_ha, ddb, 1, 0); | ||
166 | asd_ddbsite_write_word(asd_ha, ddb, INIT_CONN_TAG, 0xFFFF); | ||
167 | for (i = 0; i < SAS_ADDR_SIZE; i++) | ||
168 | asd_ddbsite_write_byte(asd_ha, ddb, DEST_SAS_ADDR+i, | ||
169 | dev->sas_addr[i]); | ||
170 | asd_ddbsite_write_word(asd_ha, ddb, SEND_QUEUE_HEAD, 0xFFFF); | ||
171 | asd_set_ddb_type(dev); | ||
172 | asd_ddbsite_write_byte(asd_ha, ddb, CONN_MASK, dev->port->phy_mask); | ||
173 | if (dev->port->oob_mode != SATA_OOB_MODE) { | ||
174 | flags |= OPEN_REQUIRED; | ||
175 | if ((dev->dev_type == SATA_DEV) || | ||
176 | (dev->tproto & SAS_PROTO_STP)) { | ||
177 | struct smp_resp *rps_resp = &dev->sata_dev.rps_resp; | ||
178 | if (rps_resp->frame_type == SMP_RESPONSE && | ||
179 | rps_resp->function == SMP_REPORT_PHY_SATA && | ||
180 | rps_resp->result == SMP_RESP_FUNC_ACC) { | ||
181 | if (rps_resp->rps.affil_valid) | ||
182 | flags |= STP_AFFIL_POL; | ||
183 | if (rps_resp->rps.affil_supp) | ||
184 | flags |= SUPPORTS_AFFIL; | ||
185 | } | ||
186 | } else { | ||
187 | flags |= CONCURRENT_CONN_SUPP; | ||
188 | if (!dev->parent && | ||
189 | (dev->dev_type == EDGE_DEV || | ||
190 | dev->dev_type == FANOUT_DEV)) | ||
191 | asd_ddbsite_write_byte(asd_ha, ddb, MAX_CCONN, | ||
192 | 4); | ||
193 | else | ||
194 | asd_ddbsite_write_byte(asd_ha, ddb, MAX_CCONN, | ||
195 | dev->pathways); | ||
196 | asd_ddbsite_write_byte(asd_ha, ddb, NUM_CTX, 1); | ||
197 | } | ||
198 | } | ||
199 | if (dev->dev_type == SATA_PM) | ||
200 | flags |= SATA_MULTIPORT; | ||
201 | asd_ddbsite_write_byte(asd_ha, ddb, DDB_TARG_FLAGS, flags); | ||
202 | |||
203 | flags = 0; | ||
204 | if (dev->tproto & SAS_PROTO_STP) | ||
205 | flags |= STP_CL_POL_NO_TX; | ||
206 | asd_ddbsite_write_byte(asd_ha, ddb, DDB_TARG_FLAGS2, flags); | ||
207 | |||
208 | asd_ddbsite_write_word(asd_ha, ddb, EXEC_QUEUE_TAIL, 0xFFFF); | ||
209 | asd_ddbsite_write_word(asd_ha, ddb, SEND_QUEUE_TAIL, 0xFFFF); | ||
210 | asd_ddbsite_write_word(asd_ha, ddb, SISTER_DDB, 0xFFFF); | ||
211 | |||
212 | if (dev->dev_type == SATA_DEV || (dev->tproto & SAS_PROTO_STP)) { | ||
213 | i = asd_init_sata(dev); | ||
214 | if (i < 0) { | ||
215 | asd_free_ddb(asd_ha, ddb); | ||
216 | return i; | ||
217 | } | ||
218 | } | ||
219 | |||
220 | if (dev->dev_type == SAS_END_DEV) { | ||
221 | struct sas_end_device *rdev = rphy_to_end_device(dev->rphy); | ||
222 | if (rdev->I_T_nexus_loss_timeout > 0) | ||
223 | asd_ddbsite_write_word(asd_ha, ddb, ITNL_TIMEOUT, | ||
224 | min(rdev->I_T_nexus_loss_timeout, | ||
225 | (u16)ITNL_TIMEOUT_CONST)); | ||
226 | else | ||
227 | asd_ddbsite_write_word(asd_ha, ddb, ITNL_TIMEOUT, | ||
228 | (u16)ITNL_TIMEOUT_CONST); | ||
229 | } | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | static int asd_init_sata_pm_table_ddb(struct domain_device *dev) | ||
234 | { | ||
235 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
236 | int ddb, i; | ||
237 | |||
238 | ddb = asd_get_ddb(asd_ha); | ||
239 | if (ddb < 0) | ||
240 | return ddb; | ||
241 | |||
242 | for (i = 0; i < 32; i += 2) | ||
243 | asd_ddbsite_write_word(asd_ha, ddb, i, 0xFFFF); | ||
244 | |||
245 | asd_ddbsite_write_word(asd_ha, (int) (unsigned long) dev->lldd_dev, | ||
246 | SISTER_DDB, ddb); | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | #define PM_PORT_FLAGS offsetof(struct asd_ddb_sata_pm_port, pm_port_flags) | ||
252 | #define PARENT_DDB offsetof(struct asd_ddb_sata_pm_port, parent_ddb) | ||
253 | |||
254 | /** | ||
255 | * asd_init_sata_pm_port_ddb -- SATA Port Multiplier Port | ||
256 | * dev: pointer to domain device | ||
257 | * | ||
258 | * For SATA Port Multiplier Ports we need to allocate one SATA Port | ||
259 | * Multiplier Port DDB and depending on whether the target on it | ||
260 | * supports SATA II NCQ, one SATA Tag DDB. | ||
261 | */ | ||
262 | static int asd_init_sata_pm_port_ddb(struct domain_device *dev) | ||
263 | { | ||
264 | int ddb, i, parent_ddb, pmtable_ddb; | ||
265 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
266 | u8 flags; | ||
267 | |||
268 | ddb = asd_get_ddb(asd_ha); | ||
269 | if (ddb < 0) | ||
270 | return ddb; | ||
271 | |||
272 | asd_set_ddb_type(dev); | ||
273 | flags = (dev->sata_dev.port_no << 4) | PM_PORT_SET; | ||
274 | asd_ddbsite_write_byte(asd_ha, ddb, PM_PORT_FLAGS, flags); | ||
275 | asd_ddbsite_write_word(asd_ha, ddb, SISTER_DDB, 0xFFFF); | ||
276 | asd_ddbsite_write_word(asd_ha, ddb, ATA_CMD_SCBPTR, 0xFFFF); | ||
277 | asd_init_sata(dev); | ||
278 | |||
279 | parent_ddb = (int) (unsigned long) dev->parent->lldd_dev; | ||
280 | asd_ddbsite_write_word(asd_ha, ddb, PARENT_DDB, parent_ddb); | ||
281 | pmtable_ddb = asd_ddbsite_read_word(asd_ha, parent_ddb, SISTER_DDB); | ||
282 | asd_ddbsite_write_word(asd_ha, pmtable_ddb, dev->sata_dev.port_no,ddb); | ||
283 | |||
284 | if (asd_ddbsite_read_byte(asd_ha, ddb, NUM_SATA_TAGS) > 0) { | ||
285 | i = asd_init_sata_tag_ddb(dev); | ||
286 | if (i < 0) { | ||
287 | asd_free_ddb(asd_ha, ddb); | ||
288 | return i; | ||
289 | } | ||
290 | } | ||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | static int asd_init_initiator_ddb(struct domain_device *dev) | ||
295 | { | ||
296 | return -ENODEV; | ||
297 | } | ||
298 | |||
299 | /** | ||
300 | * asd_init_sata_pm_ddb -- SATA Port Multiplier | ||
301 | * dev: pointer to domain device | ||
302 | * | ||
303 | * For STP and direct-attached SATA Port Multipliers we need | ||
304 | * one target port DDB entry and one SATA PM table DDB entry. | ||
305 | */ | ||
306 | static int asd_init_sata_pm_ddb(struct domain_device *dev) | ||
307 | { | ||
308 | int res = 0; | ||
309 | |||
310 | res = asd_init_target_ddb(dev); | ||
311 | if (res) | ||
312 | goto out; | ||
313 | res = asd_init_sata_pm_table_ddb(dev); | ||
314 | if (res) | ||
315 | asd_free_ddb(dev->port->ha->lldd_ha, | ||
316 | (int) (unsigned long) dev->lldd_dev); | ||
317 | out: | ||
318 | return res; | ||
319 | } | ||
320 | |||
321 | int asd_dev_found(struct domain_device *dev) | ||
322 | { | ||
323 | int res = 0; | ||
324 | |||
325 | switch (dev->dev_type) { | ||
326 | case SATA_PM: | ||
327 | res = asd_init_sata_pm_ddb(dev); | ||
328 | break; | ||
329 | case SATA_PM_PORT: | ||
330 | res = asd_init_sata_pm_port_ddb(dev); | ||
331 | break; | ||
332 | default: | ||
333 | if (dev->tproto) | ||
334 | res = asd_init_target_ddb(dev); | ||
335 | else | ||
336 | res = asd_init_initiator_ddb(dev); | ||
337 | } | ||
338 | return res; | ||
339 | } | ||
340 | |||
341 | void asd_dev_gone(struct domain_device *dev) | ||
342 | { | ||
343 | int ddb, sister_ddb; | ||
344 | struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; | ||
345 | |||
346 | ddb = (int) (unsigned long) dev->lldd_dev; | ||
347 | sister_ddb = asd_ddbsite_read_word(asd_ha, ddb, SISTER_DDB); | ||
348 | |||
349 | if (sister_ddb != 0xFFFF) | ||
350 | asd_free_ddb(asd_ha, sister_ddb); | ||
351 | asd_free_ddb(asd_ha, ddb); | ||
352 | dev->lldd_dev = NULL; | ||
353 | } | ||