1/* $NetBSD: nouveau_nvkm_subdev_bios_base.c,v 1.3 2021/12/18 23:45:38 riastradh Exp $ */ 2 3/* 4 * Copyright 2012 Red Hat Inc. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the "Software"), 8 * to deal in the Software without restriction, including without limitation 9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 * and/or sell copies of the Software, and to permit persons to whom the 11 * Software is furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 * OTHER DEALINGS IN THE SOFTWARE. 23 * 24 * Authors: Ben Skeggs 25 */ 26#include <sys/cdefs.h> 27__KERNEL_RCSID(0, "$NetBSD: nouveau_nvkm_subdev_bios_base.c,v 1.3 2021/12/18 23:45:38 riastradh Exp $"); 28 29#include "priv.h" 30 31#include <subdev/bios.h> 32#include <subdev/bios/bmp.h> 33#include <subdev/bios/bit.h> 34#include <subdev/bios/image.h> 35 36static bool 37nvbios_addr(struct nvkm_bios *bios, u32 *addr, u8 size) 38{ 39 u32 p = *addr; 40 41 if (*addr > bios->image0_size && bios->imaged_addr) { 42 *addr -= bios->image0_size; 43 *addr += bios->imaged_addr; 44 } 45 46 if (unlikely(*addr + size >= bios->size)) { 47 nvkm_error(&bios->subdev, "OOB %d %08x %08x\n", size, p, *addr); 48 return false; 49 } 50 51 return true; 52} 53 54u8 55nvbios_rd08(struct nvkm_bios *bios, u32 addr) 56{ 57 if (likely(nvbios_addr(bios, &addr, 1))) 58 return bios->data[addr]; 59 return 0x00; 60} 61 62u16 63nvbios_rd16(struct nvkm_bios *bios, u32 addr) 64{ 65 if (likely(nvbios_addr(bios, &addr, 2))) 66 return get_unaligned_le16(&bios->data[addr]); 67 return 0x0000; 68} 69 70u32 71nvbios_rd32(struct nvkm_bios *bios, u32 addr) 72{ 73 if (likely(nvbios_addr(bios, &addr, 4))) 74 return get_unaligned_le32(&bios->data[addr]); 75 return 0x00000000; 76} 77 78u8 79nvbios_checksum(const u8 *data, int size) 80{ 81 u8 sum = 0; 82 while (size--) 83 sum += *data++; 84 return sum; 85} 86 87u16 88nvbios_findstr(const u8 *data, int size, const char *str, int len) 89{ 90 int i, j; 91 92 for (i = 0; i <= (size - len); i++) { 93 for (j = 0; j < len; j++) 94 if ((char)data[i + j] != str[j]) 95 break; 96 if (j == len) 97 return i; 98 } 99 100 return 0; 101} 102 103int 104nvbios_memcmp(struct nvkm_bios *bios, u32 addr, const char *str, u32 len) 105{ 106 unsigned char c1, c2; 107 108 while (len--) { 109 c1 = nvbios_rd08(bios, addr++); 110 c2 = *(str++); 111 if (c1 != c2) 112 return c1 - c2; 113 } 114 return 0; 115} 116 117int 118nvbios_extend(struct nvkm_bios *bios, u32 length) 119{ 120 if (bios->size < length) { 121 u8 *prev = bios->data; 122 if (!(bios->data = kmalloc(length, GFP_KERNEL))) { 123 bios->data = prev; 124 return -ENOMEM; 125 } 126 memcpy(bios->data, prev, bios->size); 127 bios->size = length; 128 kfree(prev); 129 return 1; 130 } 131 return 0; 132} 133 134static void * 135nvkm_bios_dtor(struct nvkm_subdev *subdev) 136{ 137 struct nvkm_bios *bios = nvkm_bios(subdev); 138 kfree(bios->data); 139 return bios; 140} 141 142static const struct nvkm_subdev_func 143nvkm_bios = { 144 .dtor = nvkm_bios_dtor, 145}; 146 147int 148nvkm_bios_new(struct nvkm_device *device, int index, struct nvkm_bios **pbios) 149{ 150 struct nvkm_bios *bios; 151 struct nvbios_image image; 152 struct bit_entry bit_i; 153 int ret, idx = 0; 154 155 if (!(bios = *pbios = kzalloc(sizeof(*bios), GFP_KERNEL))) 156 return -ENOMEM; 157 nvkm_subdev_ctor(&nvkm_bios, device, index, &bios->subdev); 158 159 ret = nvbios_shadow(bios); 160 if (ret) 161 return ret; 162 163 /* Some tables have weird pointers that need adjustment before 164 * they're dereferenced. I'm not entirely sure why... 165 */ 166 if (nvbios_image(bios, idx++, &image)) { 167 bios->image0_size = image.size; 168 while (nvbios_image(bios, idx++, &image)) { 169 if (image.type == 0xe0) { 170 bios->imaged_addr = image.base; 171 break; 172 } 173 } 174 } 175 176 /* detect type of vbios we're dealing with */ 177 bios->bmp_offset = nvbios_findstr(bios->data, bios->size, 178 "\xff\x7f""NV\0", 5); 179 if (bios->bmp_offset) { 180 nvkm_debug(&bios->subdev, "BMP version %x.%x\n", 181 bmp_version(bios) >> 8, 182 bmp_version(bios) & 0xff); 183 } 184 185 bios->bit_offset = nvbios_findstr(bios->data, bios->size, 186 "\xff\xb8""BIT", 5); 187 if (bios->bit_offset) 188 nvkm_debug(&bios->subdev, "BIT signature found\n"); 189 190 /* determine the vbios version number */ 191 if (!bit_entry(bios, 'i', &bit_i) && bit_i.length >= 4) { 192 bios->version.major = nvbios_rd08(bios, bit_i.offset + 3); 193 bios->version.chip = nvbios_rd08(bios, bit_i.offset + 2); 194 bios->version.minor = nvbios_rd08(bios, bit_i.offset + 1); 195 bios->version.micro = nvbios_rd08(bios, bit_i.offset + 0); 196 bios->version.patch = nvbios_rd08(bios, bit_i.offset + 4); 197 } else 198 if (bmp_version(bios)) { 199 bios->version.major = nvbios_rd08(bios, bios->bmp_offset + 13); 200 bios->version.chip = nvbios_rd08(bios, bios->bmp_offset + 12); 201 bios->version.minor = nvbios_rd08(bios, bios->bmp_offset + 11); 202 bios->version.micro = nvbios_rd08(bios, bios->bmp_offset + 10); 203 } 204 205 nvkm_info(&bios->subdev, "version %02x.%02x.%02x.%02x.%02x\n", 206 bios->version.major, bios->version.chip, 207 bios->version.minor, bios->version.micro, bios->version.patch); 208 return 0; 209} 210