1 /*- 2 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer, 10 * without modification. 11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer 12 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any 13 * redistribution must be conditioned upon including a substantially 14 * similar Disclaimer requirement for further binary redistribution. 15 * 16 * NO WARRANTY 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY 20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, 22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 * THE POSSIBILITY OF SUCH DAMAGES. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include <sys/param.h> 34 #include <sys/bus.h> 35 #include <sys/malloc.h> 36 #include <sys/rman.h> 37 38 #include <machine/bus.h> 39 40 #include <dev/bhnd/bhnd.h> 41 42 #include "bhnd_nvram_private.h" 43 44 #include "bhnd_nvram_io.h" 45 #include "bhnd_nvram_iovar.h" 46 47 /** 48 * BHND resource-backed NVRAM I/O context. 49 */ 50 struct bhnd_nvram_iores { 51 struct bhnd_nvram_io io; /**< common I/O instance state */ 52 struct bhnd_resource *res; /**< backing resource (borrowed ref) */ 53 size_t offset; /**< offset within res */ 54 size_t size; /**< size relative to the base offset */ 55 u_int bus_width; /**< data type byte width to be used 56 when performing bus operations 57 on res. (1, 2, or 4 bytes) */ 58 }; 59 60 BHND_NVRAM_IOPS_DEFN(iores); 61 62 /** 63 * Allocate and return a new I/O context backed by a borrowed reference to @p r. 64 * 65 * The caller is responsible for deallocating the returned I/O context via 66 * bhnd_nvram_io_free(). 67 * 68 * @param r The resource to be mapped by the returned I/O 69 * context. 70 * @param offset Offset 71 * @param bus_width The required I/O width (1, 2, or 4 bytes) to be 72 * used when reading from @p r. 73 * 74 * @retval bhnd_nvram_io success. 75 * @retval NULL if allocation fails, or an invalid argument 76 * is supplied. 77 */ 78 struct bhnd_nvram_io * 79 bhnd_nvram_iores_new(struct bhnd_resource *r, bus_size_t offset, 80 bus_size_t size, u_int bus_width) 81 { 82 struct bhnd_nvram_iores *iores; 83 rman_res_t r_start, r_size; 84 85 /* Verify the bus width */ 86 switch (bus_width) { 87 case 1: 88 case 2: 89 case 4: 90 /* valid */ 91 break; 92 default: 93 BHND_NV_LOG("invalid bus width %u\n", bus_width); 94 return (NULL); 95 } 96 97 /* offset/size must not exceed our internal size_t representation, 98 * or our bus_size_t usage (note that BUS_SPACE_MAXSIZE may be less 99 * than 2^(sizeof(bus_size_t) * 32). */ 100 if (size > SIZE_MAX || offset > SIZE_MAX) { 101 BHND_NV_LOG("offset %#jx+%#jx exceeds SIZE_MAX\n", 102 (uintmax_t)offset, (uintmax_t)offset); 103 return (NULL); 104 } 105 106 if (size > BUS_SPACE_MAXSIZE || offset > BUS_SPACE_MAXSIZE) 107 { 108 BHND_NV_LOG("offset %#jx+%#jx exceeds BUS_SPACE_MAXSIZE\n", 109 (uintmax_t)offset, (uintmax_t)offset); 110 return (NULL); 111 } 112 113 /* offset/size fall within the resource's mapped range */ 114 r_size = rman_get_size(r->res); 115 r_start = rman_get_start(r->res); 116 if (r_size < offset || r_size < size || r_size - size < offset) 117 return (NULL); 118 119 /* offset/size must be bus_width aligned */ 120 if ((r_start + offset) % bus_width != 0) { 121 BHND_NV_LOG("base address %#jx+%#jx not aligned to bus width " 122 "%u\n", (uintmax_t)r_start, (uintmax_t)offset, bus_width); 123 return (NULL); 124 } 125 126 if (size % bus_width != 0) { 127 BHND_NV_LOG("size %#jx not aligned to bus width %u\n", 128 (uintmax_t)size, bus_width); 129 return (NULL); 130 } 131 132 /* Allocate and return the I/O context */ 133 iores = malloc(sizeof(*iores), M_BHND_NVRAM, M_WAITOK); 134 iores->io.iops = &bhnd_nvram_iores_ops; 135 iores->res = r; 136 iores->offset = offset; 137 iores->size = size; 138 iores->bus_width = bus_width; 139 140 return (&iores->io); 141 } 142 143 static void 144 bhnd_nvram_iores_free(struct bhnd_nvram_io *io) 145 { 146 free(io, M_BHND_NVRAM); 147 } 148 149 static size_t 150 bhnd_nvram_iores_getsize(struct bhnd_nvram_io *io) 151 { 152 struct bhnd_nvram_iores *iores = (struct bhnd_nvram_iores *)io; 153 return (iores->size); 154 } 155 156 static int 157 bhnd_nvram_iores_setsize(struct bhnd_nvram_io *io, size_t size) 158 { 159 /* unsupported */ 160 return (ENODEV); 161 } 162 163 static int 164 bhnd_nvram_iores_read_ptr(struct bhnd_nvram_io *io, size_t offset, 165 const void **ptr, size_t nbytes, size_t *navail) 166 { 167 /* unsupported */ 168 return (ENODEV); 169 } 170 171 static int 172 bhnd_nvram_iores_write_ptr(struct bhnd_nvram_io *io, size_t offset, 173 void **ptr, size_t nbytes, size_t *navail) 174 { 175 /* unsupported */ 176 return (ENODEV); 177 } 178 179 /** 180 * Validate @p offset and @p nbytes: 181 * 182 * - Verify that @p offset is mapped by the backing resource. 183 * - If less than @p nbytes are available at @p offset, write the actual number 184 * of bytes available to @p nbytes. 185 * - Verify that @p offset + @p nbytes are correctly aligned. 186 */ 187 static int 188 bhnd_nvram_iores_validate_req(struct bhnd_nvram_iores *iores, size_t offset, 189 size_t *nbytes) 190 { 191 /* Verify offset falls within the resource range */ 192 if (offset > iores->size) 193 return (ENXIO); 194 195 /* Check for eof */ 196 if (offset == iores->size) { 197 *nbytes = 0; 198 return (0); 199 } 200 201 /* Verify offset alignment */ 202 if (offset % iores->bus_width != 0) 203 return (EFAULT); 204 205 /* Limit nbytes to available range and verify size alignment */ 206 *nbytes = ummin(*nbytes, iores->size - offset); 207 if (*nbytes < iores->bus_width && *nbytes % iores->bus_width != 0) 208 return (EFAULT); 209 210 return (0); 211 } 212 213 214 static int 215 bhnd_nvram_iores_read(struct bhnd_nvram_io *io, size_t offset, void *buffer, 216 size_t nbytes) 217 { 218 struct bhnd_nvram_iores *iores; 219 bus_size_t r_offset; 220 size_t navail; 221 int error; 222 223 iores = (struct bhnd_nvram_iores *)io; 224 225 /* Validate the request and determine the actual number of readable 226 * bytes */ 227 navail = nbytes; 228 if ((error = bhnd_nvram_iores_validate_req(iores, offset, &navail))) 229 return (error); 230 231 /* At least nbytes must be readable */ 232 if (navail < nbytes) 233 return (ENXIO); 234 235 /* Handle zero length read */ 236 if (nbytes == 0) 237 return (0); 238 239 /* Determine actual resource offset and perform the read */ 240 r_offset = iores->offset + offset; 241 switch (iores->bus_width) { 242 case 1: 243 bhnd_bus_read_region_stream_1(iores->res, r_offset, buffer, 244 nbytes); 245 break; 246 case 2: 247 bhnd_bus_read_region_stream_2(iores->res, r_offset, buffer, 248 nbytes / 2); 249 break; 250 case 4: 251 bhnd_bus_read_region_stream_4(iores->res, r_offset, buffer, 252 nbytes / 4); 253 break; 254 default: 255 panic("unreachable!"); 256 } 257 258 return (0); 259 } 260 261 static int 262 bhnd_nvram_iores_write(struct bhnd_nvram_io *io, size_t offset, 263 void *buffer, size_t nbytes) 264 { 265 struct bhnd_nvram_iores *iores; 266 size_t navail; 267 bus_size_t r_offset; 268 int error; 269 270 iores = (struct bhnd_nvram_iores *)io; 271 272 /* Validate the request and determine the actual number of writable 273 * bytes */ 274 navail = nbytes; 275 if ((error = bhnd_nvram_iores_validate_req(iores, offset, &navail))) 276 return (error); 277 278 /* At least nbytes must be writable */ 279 if (navail < nbytes) 280 return (ENXIO); 281 282 /* Determine actual resource offset and perform the write */ 283 r_offset = iores->offset + offset; 284 switch (iores->bus_width) { 285 case 1: 286 bhnd_bus_write_region_stream_1(iores->res, r_offset, buffer, 287 nbytes); 288 break; 289 case 2: 290 bhnd_bus_write_region_stream_2(iores->res, r_offset, buffer, 291 nbytes / 2); 292 break; 293 case 4: 294 bhnd_bus_write_region_stream_4(iores->res, r_offset, buffer, 295 nbytes / 4); 296 break; 297 default: 298 panic("unreachable!"); 299 } 300 301 return (0); 302 } 303