Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /************************************************************************/ |
| 2 | /* File iSeries_vpdInfo.c created by Allan Trautman on Fri Feb 2 2001. */ |
| 3 | /************************************************************************/ |
| 4 | /* This code gets the card location of the hardware */ |
| 5 | /* Copyright (C) 20yy <Allan H Trautman> <IBM Corp> */ |
| 6 | /* */ |
| 7 | /* This program is free software; you can redistribute it and/or modify */ |
| 8 | /* it under the terms of the GNU General Public License as published by */ |
| 9 | /* the Free Software Foundation; either version 2 of the License, or */ |
| 10 | /* (at your option) any later version. */ |
| 11 | /* */ |
| 12 | /* This program is distributed in the hope that it will be useful, */ |
| 13 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ |
| 14 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ |
| 15 | /* GNU General Public License for more details. */ |
| 16 | /* */ |
| 17 | /* You should have received a copy of the GNU General Public License */ |
| 18 | /* along with this program; if not, write to the: */ |
| 19 | /* Free Software Foundation, Inc., */ |
| 20 | /* 59 Temple Place, Suite 330, */ |
| 21 | /* Boston, MA 02111-1307 USA */ |
| 22 | /************************************************************************/ |
| 23 | /* Change Activity: */ |
| 24 | /* Created, Feb 2, 2001 */ |
| 25 | /* Ported to ppc64, August 20, 2001 */ |
| 26 | /* End Change Activity */ |
| 27 | /************************************************************************/ |
| 28 | #include <linux/config.h> |
| 29 | #include <linux/init.h> |
| 30 | #include <linux/module.h> |
| 31 | #include <linux/pci.h> |
| 32 | #include <asm/types.h> |
| 33 | #include <asm/resource.h> |
| 34 | |
| 35 | #include <asm/iSeries/HvCallPci.h> |
| 36 | #include <asm/iSeries/HvTypes.h> |
| 37 | #include <asm/iSeries/mf.h> |
| 38 | #include <asm/iSeries/LparData.h> |
| 39 | #include <asm/iSeries/iSeries_pci.h> |
| 40 | #include "pci.h" |
| 41 | |
| 42 | /* |
| 43 | * Size of Bus VPD data |
| 44 | */ |
| 45 | #define BUS_VPDSIZE 1024 |
| 46 | /* |
| 47 | * Bus Vpd Tags |
| 48 | */ |
| 49 | #define VpdEndOfDataTag 0x78 |
| 50 | #define VpdEndOfAreaTag 0x79 |
| 51 | #define VpdIdStringTag 0x82 |
| 52 | #define VpdVendorAreaTag 0x84 |
| 53 | /* |
| 54 | * Mfg Area Tags |
| 55 | */ |
| 56 | #define VpdFruFlag 0x4647 // "FG" |
| 57 | #define VpdFruFrameId 0x4649 // "FI" |
| 58 | #define VpdSlotMapFormat 0x4D46 // "MF" |
| 59 | #define VpdAsmPartNumber 0x504E // "PN" |
| 60 | #define VpdFruSerial 0x534E // "SN" |
| 61 | #define VpdSlotMap 0x534D // "SM" |
| 62 | |
| 63 | /* |
| 64 | * Structures of the areas |
| 65 | */ |
| 66 | struct MfgVpdAreaStruct { |
| 67 | u16 Tag; |
| 68 | u8 TagLength; |
| 69 | u8 AreaData1; |
| 70 | u8 AreaData2; |
| 71 | }; |
| 72 | typedef struct MfgVpdAreaStruct MfgArea; |
| 73 | #define MFG_ENTRY_SIZE 3 |
| 74 | |
| 75 | struct SlotMapStruct { |
| 76 | u8 AgentId; |
| 77 | u8 SecondaryAgentId; |
| 78 | u8 PhbId; |
| 79 | char CardLocation[3]; |
| 80 | char Parms[8]; |
| 81 | char Reserved[2]; |
| 82 | }; |
| 83 | typedef struct SlotMapStruct SlotMap; |
| 84 | #define SLOT_ENTRY_SIZE 16 |
| 85 | |
| 86 | /* |
| 87 | * Formats the device information. |
| 88 | * - Pass in pci_dev* pointer to the device. |
| 89 | * - Pass in buffer to place the data. Danger here is the buffer must |
| 90 | * be as big as the client says it is. Should be at least 128 bytes. |
| 91 | * Return will the length of the string data put in the buffer. |
| 92 | * Format: |
| 93 | * PCI: Bus 0, Device 26, Vendor 0x12AE Frame 1, Card C10 Ethernet |
| 94 | * controller |
| 95 | */ |
| 96 | int iSeries_Device_Information(struct pci_dev *PciDev, char *buffer, |
| 97 | int BufferSize) |
| 98 | { |
| 99 | struct iSeries_Device_Node *DevNode = |
| 100 | (struct iSeries_Device_Node *)PciDev->sysdata; |
| 101 | int len; |
| 102 | |
| 103 | if (DevNode == NULL) |
| 104 | return sprintf(buffer, |
| 105 | "PCI: iSeries_Device_Information DevNode is NULL"); |
| 106 | |
| 107 | if (BufferSize < 128) |
| 108 | return 0; |
| 109 | |
| 110 | len = sprintf(buffer, "PCI: Bus%3d, Device%3d, Vendor %04X ", |
| 111 | ISERIES_BUS(DevNode), PCI_SLOT(PciDev->devfn), |
| 112 | PciDev->vendor); |
| 113 | len += sprintf(buffer + len, "Frame%3d, Card %4s ", |
| 114 | DevNode->FrameId, DevNode->CardLocation); |
| 115 | #ifdef CONFIG_PCI |
| 116 | if (pci_class_name(PciDev->class >> 8) == 0) |
| 117 | len += sprintf(buffer + len, "0x%04X ", |
| 118 | (int)(PciDev->class >> 8)); |
| 119 | else |
| 120 | len += sprintf(buffer + len, "%s", |
| 121 | pci_class_name(PciDev->class >> 8)); |
| 122 | #endif |
| 123 | return len; |
| 124 | } |
| 125 | |
| 126 | /* |
| 127 | * Parse the Slot Area |
| 128 | */ |
| 129 | void iSeries_Parse_SlotArea(SlotMap *MapPtr, int MapLen, |
| 130 | struct iSeries_Device_Node *DevNode) |
| 131 | { |
| 132 | int SlotMapLen = MapLen; |
| 133 | SlotMap *SlotMapPtr = MapPtr; |
| 134 | |
| 135 | /* |
| 136 | * Parse Slot label until we find the one requrested |
| 137 | */ |
| 138 | while (SlotMapLen > 0) { |
| 139 | if (SlotMapPtr->AgentId == DevNode->AgentId ) { |
| 140 | /* |
| 141 | * If Phb wasn't found, grab the entry first one found. |
| 142 | */ |
| 143 | if (DevNode->PhbId == 0xff) |
| 144 | DevNode->PhbId = SlotMapPtr->PhbId; |
| 145 | /* Found it, extract the data. */ |
| 146 | if (SlotMapPtr->PhbId == DevNode->PhbId ) { |
| 147 | memcpy(&DevNode->CardLocation, |
| 148 | &SlotMapPtr->CardLocation, 3); |
| 149 | DevNode->CardLocation[3] = 0; |
| 150 | break; |
| 151 | } |
| 152 | } |
| 153 | /* Point to the next Slot */ |
| 154 | SlotMapPtr = (SlotMap *)((char *)SlotMapPtr + SLOT_ENTRY_SIZE); |
| 155 | SlotMapLen -= SLOT_ENTRY_SIZE; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /* |
| 160 | * Parse the Mfg Area |
| 161 | */ |
| 162 | static void iSeries_Parse_MfgArea(u8 *AreaData, int AreaLen, |
| 163 | struct iSeries_Device_Node *DevNode) |
| 164 | { |
| 165 | MfgArea *MfgAreaPtr = (MfgArea *)AreaData; |
| 166 | int MfgAreaLen = AreaLen; |
| 167 | u16 SlotMapFmt = 0; |
| 168 | |
| 169 | /* Parse Mfg Data */ |
| 170 | while (MfgAreaLen > 0) { |
| 171 | int MfgTagLen = MfgAreaPtr->TagLength; |
| 172 | /* Frame ID (FI 4649020310 ) */ |
| 173 | if (MfgAreaPtr->Tag == VpdFruFrameId) /* FI */ |
| 174 | DevNode->FrameId = MfgAreaPtr->AreaData1; |
| 175 | /* Slot Map Format (MF 4D46020004 ) */ |
| 176 | else if (MfgAreaPtr->Tag == VpdSlotMapFormat) /* MF */ |
| 177 | SlotMapFmt = (MfgAreaPtr->AreaData1 * 256) |
| 178 | + MfgAreaPtr->AreaData2; |
| 179 | /* Slot Map (SM 534D90 */ |
| 180 | else if (MfgAreaPtr->Tag == VpdSlotMap) { /* SM */ |
| 181 | SlotMap *SlotMapPtr; |
| 182 | |
| 183 | if (SlotMapFmt == 0x1004) |
| 184 | SlotMapPtr = (SlotMap *)((char *)MfgAreaPtr |
| 185 | + MFG_ENTRY_SIZE + 1); |
| 186 | else |
| 187 | SlotMapPtr = (SlotMap *)((char *)MfgAreaPtr |
| 188 | + MFG_ENTRY_SIZE); |
| 189 | iSeries_Parse_SlotArea(SlotMapPtr, MfgTagLen, DevNode); |
| 190 | } |
| 191 | /* |
| 192 | * Point to the next Mfg Area |
| 193 | * Use defined size, sizeof give wrong answer |
| 194 | */ |
| 195 | MfgAreaPtr = (MfgArea *)((char *)MfgAreaPtr + MfgTagLen |
| 196 | + MFG_ENTRY_SIZE); |
| 197 | MfgAreaLen -= (MfgTagLen + MFG_ENTRY_SIZE); |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | /* |
| 202 | * Look for "BUS".. Data is not Null terminated. |
| 203 | * PHBID of 0xFF indicates PHB was not found in VPD Data. |
| 204 | */ |
| 205 | static int iSeries_Parse_PhbId(u8 *AreaPtr, int AreaLength) |
| 206 | { |
| 207 | u8 *PhbPtr = AreaPtr; |
| 208 | int DataLen = AreaLength; |
| 209 | char PhbId = 0xFF; |
| 210 | |
| 211 | while (DataLen > 0) { |
| 212 | if ((*PhbPtr == 'B') && (*(PhbPtr + 1) == 'U') |
| 213 | && (*(PhbPtr + 2) == 'S')) { |
| 214 | PhbPtr += 3; |
| 215 | while (*PhbPtr == ' ') |
| 216 | ++PhbPtr; |
| 217 | PhbId = (*PhbPtr & 0x0F); |
| 218 | break; |
| 219 | } |
| 220 | ++PhbPtr; |
| 221 | --DataLen; |
| 222 | } |
| 223 | return PhbId; |
| 224 | } |
| 225 | |
| 226 | /* |
| 227 | * Parse out the VPD Areas |
| 228 | */ |
| 229 | static void iSeries_Parse_Vpd(u8 *VpdData, int VpdDataLen, |
| 230 | struct iSeries_Device_Node *DevNode) |
| 231 | { |
| 232 | u8 *TagPtr = VpdData; |
| 233 | int DataLen = VpdDataLen - 3; |
| 234 | |
| 235 | while ((*TagPtr != VpdEndOfAreaTag) && (DataLen > 0)) { |
| 236 | int AreaLen = *(TagPtr + 1) + (*(TagPtr + 2) * 256); |
| 237 | u8 *AreaData = TagPtr + 3; |
| 238 | |
| 239 | if (*TagPtr == VpdIdStringTag) |
| 240 | DevNode->PhbId = iSeries_Parse_PhbId(AreaData, AreaLen); |
| 241 | else if (*TagPtr == VpdVendorAreaTag) |
| 242 | iSeries_Parse_MfgArea(AreaData, AreaLen, DevNode); |
| 243 | /* Point to next Area. */ |
| 244 | TagPtr = AreaData + AreaLen; |
| 245 | DataLen -= AreaLen; |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | void iSeries_Get_Location_Code(struct iSeries_Device_Node *DevNode) |
| 250 | { |
| 251 | int BusVpdLen = 0; |
| 252 | u8 *BusVpdPtr = (u8 *)kmalloc(BUS_VPDSIZE, GFP_KERNEL); |
| 253 | |
| 254 | if (BusVpdPtr == NULL) { |
| 255 | printk("PCI: Bus VPD Buffer allocation failure.\n"); |
| 256 | return; |
| 257 | } |
| 258 | BusVpdLen = HvCallPci_getBusVpd(ISERIES_BUS(DevNode), |
| 259 | ISERIES_HV_ADDR(BusVpdPtr), |
| 260 | BUS_VPDSIZE); |
| 261 | if (BusVpdLen == 0) { |
| 262 | kfree(BusVpdPtr); |
| 263 | printk("PCI: Bus VPD Buffer zero length.\n"); |
| 264 | return; |
| 265 | } |
| 266 | /* printk("PCI: BusVpdPtr: %p, %d\n",BusVpdPtr, BusVpdLen); */ |
| 267 | /* Make sure this is what I think it is */ |
| 268 | if (*BusVpdPtr != VpdIdStringTag) { /* 0x82 */ |
| 269 | printk("PCI: Bus VPD Buffer missing starting tag.\n"); |
| 270 | kfree(BusVpdPtr); |
| 271 | return; |
| 272 | } |
| 273 | iSeries_Parse_Vpd(BusVpdPtr,BusVpdLen, DevNode); |
| 274 | sprintf(DevNode->Location, "Frame%3d, Card %-4s", DevNode->FrameId, |
| 275 | DevNode->CardLocation); |
| 276 | kfree(BusVpdPtr); |
| 277 | } |