xref: /haiku/src/add-ons/kernel/bus_managers/scsi/scatter_gather.cpp (revision 72156a402f54ea4be9dc3e3e9704c612f7d9ad16)
1 /*
2  * Copyright 2004-2008, Haiku, Inc. All RightsReserved.
3  * Copyright 2002-2003, Thomas Kurschel. All rights reserved.
4  *
5  * Distributed under the terms of the MIT License.
6  */
7 
8 /*!
9 	Creates temporary Scatter/Gather table if the peripheral
10 	driver has provided a simple pointer only.
11 */
12 
13 #include "scsi_internal.h"
14 #include "KernelExport_ext.h"
15 
16 #include <string.h>
17 #include <iovec.h>
18 
19 #include <algorithm>
20 
21 
22 static locked_pool_cookie temp_sg_pool;
23 
24 
25 static bool
26 fill_temp_sg(scsi_ccb *ccb)
27 {
28 	status_t res;
29 	scsi_bus_info *bus = ccb->bus;
30 	uint32 dma_boundary = bus->dma_params.dma_boundary;
31 	uint32 max_sg_block_size = bus->dma_params.max_sg_block_size;
32 	uint32 max_sg_blocks = std::min(bus->dma_params.max_sg_blocks,
33 		(uint32)MAX_TEMP_SG_FRAGMENTS);
34 	iovec vec = {
35 		ccb->data,
36 		ccb->data_length
37 	};
38 	size_t num_entries;
39 	size_t mapped_len;
40 	uint32 cur_idx;
41 	physical_entry *temp_sg = (physical_entry *)ccb->sg_list;
42 
43 	res = get_iovec_memory_map(&vec, 1, 0, ccb->data_length, temp_sg, max_sg_blocks,
44 				&num_entries, &mapped_len);
45 
46 	if (res != B_OK) {
47 		SHOW_ERROR(2, "cannot create temporary S/G list for IO request (%s)", strerror(res));
48 		return false;
49 	}
50 
51 	if (mapped_len != ccb->data_length)
52 		goto too_complex;
53 
54 	if (dma_boundary != ~0UL || ccb->data_length > max_sg_block_size) {
55 		// S/G list may not be controller-compatible:
56 		// we have to split offending entries
57 		SHOW_FLOW(3, "Checking violation of dma boundary 0x%x and entry size 0x%x",
58 			(int)dma_boundary, (int)max_sg_block_size);
59 
60 		for (cur_idx = 0; cur_idx < num_entries; ++cur_idx) {
61 			addr_t max_len;
62 
63 			// calculate space upto next dma boundary crossing
64 			max_len = (dma_boundary + 1) -
65 				(temp_sg[cur_idx].address & dma_boundary);
66 			// restrict size per sg item
67 			max_len = std::min(max_len, max_sg_block_size);
68 
69 			SHOW_FLOW(4, "addr=%#" B_PRIxPHYSADDR ", size=%x, max_len=%x, "
70 				"idx=%d, num=%d", temp_sg[cur_idx].address,
71 				(int)temp_sg[cur_idx].size, (int)max_len, (int)cur_idx,
72 				(int)num_entries);
73 
74 			if (max_len < temp_sg[cur_idx].size) {
75 				// split sg block
76 				if (++num_entries > max_sg_blocks)
77 					goto too_complex;
78 
79 				memmove(&temp_sg[cur_idx + 1], &temp_sg[cur_idx],
80 					(num_entries - 1 - cur_idx) * sizeof(physical_entry));
81 
82 				temp_sg[cur_idx].size = max_len;
83 				temp_sg[cur_idx + 1].address
84 					= temp_sg[cur_idx + 1].address + max_len;
85 				temp_sg[cur_idx + 1].size -= max_len;
86 			}
87 		}
88 	}
89 
90 	ccb->sg_count = num_entries;
91 
92 	return true;
93 
94 too_complex:
95 	SHOW_ERROR( 2, "S/G list to complex for IO request (max %d entries)",
96 		MAX_TEMP_SG_FRAGMENTS );
97 
98 	return false;
99 }
100 
101 
102 /** create temporary SG for request */
103 
104 bool
105 create_temp_sg(scsi_ccb *ccb)
106 {
107 	physical_entry *temp_sg;
108 	status_t res;
109 
110 	SHOW_FLOW(3, "ccb=%p, data=%p, data_length=%lu", ccb, ccb->data, ccb->data_length);
111 
112 	ccb->sg_list = temp_sg = (physical_entry*)locked_pool->alloc(temp_sg_pool);
113 	if (temp_sg == NULL) {
114 		SHOW_ERROR0(2, "cannot allocate memory for IO request!");
115 		return false;
116 	}
117 
118 	res = lock_memory(ccb->data, ccb->data_length, B_DMA_IO
119 		| ((ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN ? B_READ_DEVICE : 0));
120 
121 	if (res != B_OK) {
122 		SHOW_ERROR(2, "cannot lock memory for IO request (%s)", strerror(res));
123 		goto err;
124 	}
125 
126 	if (fill_temp_sg(ccb))
127 		// this is the success path
128 		return true;
129 
130 	unlock_memory(ccb->data, ccb->data_length, B_DMA_IO
131 		| ((ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN ? B_READ_DEVICE : 0));
132 
133 err:
134 	locked_pool->free(temp_sg_pool, temp_sg);
135 	return false;
136 }
137 
138 
139 /** cleanup temporary SG list */
140 
141 void
142 uninit_temp_sg(void)
143 {
144 	locked_pool->destroy(temp_sg_pool);
145 }
146 
147 
148 /** destroy SG list buffer */
149 
150 void
151 cleanup_tmp_sg(scsi_ccb *ccb)
152 {
153 	status_t res;
154 
155 	SHOW_FLOW(3, "ccb=%p, data=%p, data_length=%d",
156 		ccb, ccb->data, (int)ccb->data_length);
157 
158 	res = unlock_memory(ccb->data, ccb->data_length, B_DMA_IO
159 		|  ((ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN ? B_READ_DEVICE : 0));
160 
161 	if (res != B_OK) {
162 		SHOW_FLOW0(3, "Cannot unlock previously locked memory!");
163 		panic("Cannot unlock previously locked memory!");
164 	}
165 
166 	locked_pool->free(temp_sg_pool, (physical_entry *)ccb->sg_list);
167 
168 	// restore previous state
169 	ccb->sg_list = NULL;
170 }
171 
172 
173 /** create SG list buffer */
174 
175 int
176 init_temp_sg(void)
177 {
178 	temp_sg_pool = locked_pool->create(
179 		MAX_TEMP_SG_FRAGMENTS * sizeof(physical_entry),
180 		sizeof(physical_entry) - 1, 0,
181 		B_PAGE_SIZE, MAX_TEMP_SG_LISTS, 1,
182 		"scsi_temp_sg_pool", B_CONTIGUOUS, NULL, NULL, NULL);
183 
184 	if (temp_sg_pool == NULL)
185 		return B_NO_MEMORY;
186 
187 	return B_OK;
188 }
189 
190