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