1 /*
2  * Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 #include <stdio.h>
27 #include <errno.h>
28 #include <ipxe/nvs.h>
29 #include <ipxe/pci.h>
30 #include <ipxe/pcivpd.h>
31 #include <ipxe/nvo.h>
32 #include <ipxe/nvsvpd.h>
33 
34 /** @file
35  *
36  * Non-Volatile Storage using Vital Product Data
37  *
38  */
39 
40 /**
41  * Read from VPD field
42  *
43  * @v nvs		NVS device
44  * @v field		VPD field descriptor
45  * @v data		Data buffer
46  * @v len		Length of data buffer
47  * @ret rc		Return status code
48  */
nvs_vpd_read(struct nvs_device * nvs,unsigned int field,void * data,size_t len)49 static int nvs_vpd_read ( struct nvs_device *nvs, unsigned int field,
50 			  void *data, size_t len ) {
51 	struct nvs_vpd_device *nvsvpd =
52 		container_of ( nvs, struct nvs_vpd_device, nvs );
53 	struct pci_device *pci = nvsvpd->vpd.pci;
54 	unsigned int address;
55 	size_t max_len;
56 	int rc;
57 
58 	/* Allow reading non-existent field */
59 	if ( len == 0 )
60 		return 0;
61 
62 	/* Locate VPD field */
63 	if ( ( rc = pci_vpd_find ( &nvsvpd->vpd, field, &address,
64 				   &max_len ) ) != 0 ) {
65 		DBGC ( pci, PCI_FMT " NVS VPD could not locate field "
66 		       PCI_VPD_FIELD_FMT ": %s\n", PCI_ARGS ( pci ),
67 		       PCI_VPD_FIELD_ARGS ( field ), strerror ( rc ) );
68 		return rc;
69 	}
70 
71 	/* Sanity check */
72 	if ( len > max_len ) {
73 		DBGC ( pci, PCI_FMT " NVS VPD cannot read %#02zx bytes "
74 		       "beyond field " PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n",
75 		       PCI_ARGS ( pci ), len, PCI_VPD_FIELD_ARGS ( field ),
76 		       address, ( address + max_len ) );
77 		return -ENXIO;
78 	}
79 
80 	/* Read from VPD field */
81 	if ( ( rc = pci_vpd_read ( &nvsvpd->vpd, address, data, len ) ) != 0 ) {
82 		DBGC ( pci, PCI_FMT " NVS VPD could not read field "
83 		       PCI_VPD_FIELD_FMT " at [%04x,%04zx): %s\n",
84 		       PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ),
85 		       address, ( address + len ), strerror ( rc ) );
86 		return rc;
87 	}
88 
89 	return 0;
90 }
91 
92 /**
93  * Write to VPD field
94  *
95  * @v nvs		NVS device
96  * @v field		VPD field descriptor
97  * @v data		Data buffer
98  * @v len		Length of data buffer
99  * @ret rc		Return status code
100  */
nvs_vpd_write(struct nvs_device * nvs,unsigned int field,const void * data,size_t len)101 static int nvs_vpd_write ( struct nvs_device *nvs, unsigned int field,
102 			   const void *data, size_t len ) {
103 	struct nvs_vpd_device *nvsvpd =
104 		container_of ( nvs, struct nvs_vpd_device, nvs );
105 	struct pci_device *pci = nvsvpd->vpd.pci;
106 	unsigned int address;
107 	size_t max_len;
108 	int rc;
109 
110 	/* Locate VPD field */
111 	if ( ( rc = pci_vpd_find ( &nvsvpd->vpd, field, &address,
112 				   &max_len ) ) != 0 ) {
113 		DBGC ( pci, PCI_FMT " NVS VPD could not locate field "
114 		       PCI_VPD_FIELD_FMT ": %s\n", PCI_ARGS ( pci ),
115 		       PCI_VPD_FIELD_ARGS ( field ), strerror ( rc ) );
116 		return rc;
117 	}
118 
119 	/* Sanity check */
120 	if ( len > max_len ) {
121 		DBGC ( pci, PCI_FMT " NVS VPD cannot write %#02zx bytes "
122 		       "beyond field " PCI_VPD_FIELD_FMT " at [%04x,%04zx)\n",
123 		       PCI_ARGS ( pci ), len, PCI_VPD_FIELD_ARGS ( field ),
124 		       address, ( address + max_len ) );
125 		return -ENXIO;
126 	}
127 
128 	/* Write field */
129 	if ( ( rc = pci_vpd_write ( &nvsvpd->vpd, address, data,
130 				    len ) ) != 0 ) {
131 		DBGC ( pci, PCI_FMT " NVS VPD could not write field "
132 		       PCI_VPD_FIELD_FMT " at [%04x,%04zx): %s\n",
133 		       PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ),
134 		       address, ( address + len ), strerror ( rc ) );
135 		return rc;
136 	}
137 
138 	return 0;
139 }
140 
141 /**
142  * Resize VPD field
143  *
144  * @v nvs		NVS device
145  * @v field		VPD field descriptor
146  * @v data		Data buffer
147  * @v len		Length of data buffer
148  * @ret rc		Return status code
149  */
nvs_vpd_resize(struct nvs_device * nvs,unsigned int field,size_t len)150 static int nvs_vpd_resize ( struct nvs_device *nvs, unsigned int field,
151 			    size_t len ) {
152 	struct nvs_vpd_device *nvsvpd =
153 		container_of ( nvs, struct nvs_vpd_device, nvs );
154 	struct pci_device *pci = nvsvpd->vpd.pci;
155 	unsigned int address;
156 	int rc;
157 
158 	/* Resize field */
159 	if ( ( rc = pci_vpd_resize ( &nvsvpd->vpd, field, len,
160 				     &address ) ) != 0 ) {
161 		DBGC ( pci, PCI_FMT " NVS VPD could not resize field "
162 		       PCI_VPD_FIELD_FMT " to %#02zx bytes: %s\n",
163 		       PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ),
164 		       len, strerror ( rc ) );
165 		return rc;
166 	}
167 
168 	return 0;
169 }
170 
171 /**
172  * Initialise NVS VPD device
173  *
174  * @v nvsvpd		NVS VPD device
175  * @v pci		PCI device
176  * @ret rc		Return status code
177  */
nvs_vpd_init(struct nvs_vpd_device * nvsvpd,struct pci_device * pci)178 int nvs_vpd_init ( struct nvs_vpd_device *nvsvpd, struct pci_device *pci ) {
179 	int rc;
180 
181 	/* Initialise VPD device */
182 	if ( ( rc = pci_vpd_init ( &nvsvpd->vpd, pci ) ) != 0 ) {
183 		DBGC ( pci, PCI_FMT " NVS could not initialise "
184 		       "VPD: %s\n", PCI_ARGS ( pci ), strerror ( rc ) );
185 		return rc;
186 	}
187 
188 	/* Initialise NVS device */
189 	nvsvpd->nvs.read = nvs_vpd_read;
190 	nvsvpd->nvs.write = nvs_vpd_write;
191 
192 	return 0;
193 }
194 
195 /**
196  * Resize non-volatile option storage within NVS VPD device
197  *
198  * @v nvo		Non-volatile options block
199  * @v len		New length
200  * @ret rc		Return status code
201  */
nvs_vpd_nvo_resize(struct nvo_block * nvo,size_t len)202 static int nvs_vpd_nvo_resize ( struct nvo_block *nvo, size_t len ) {
203 	int rc;
204 
205 	/* Resize VPD field */
206 	if ( ( rc = nvs_vpd_resize ( nvo->nvs, nvo->address, len ) ) != 0 )
207 		return rc;
208 
209 	return 0;
210 }
211 
212 /**
213  * Initialise non-volatile option storage within NVS VPD device
214  *
215  * @v nvsvpd		NVS VPD device
216  * @v field		VPD field descriptor
217  * @v nvo		Non-volatile options block
218  * @v refcnt		Containing object reference counter, or NULL
219  */
nvs_vpd_nvo_init(struct nvs_vpd_device * nvsvpd,unsigned int field,struct nvo_block * nvo,struct refcnt * refcnt)220 void nvs_vpd_nvo_init ( struct nvs_vpd_device *nvsvpd, unsigned int field,
221 			struct nvo_block *nvo, struct refcnt *refcnt ) {
222 	struct pci_device *pci = nvsvpd->vpd.pci;
223 	unsigned int address;
224 	size_t len;
225 	int rc;
226 
227 	/* Locate VPD field, if present */
228 	if ( ( rc = pci_vpd_find ( &nvsvpd->vpd, field, &address,
229 				   &len ) ) != 0 ) {
230 		DBGC ( pci, PCI_FMT " NVS VPD field " PCI_VPD_FIELD_FMT
231 		       " not present; assuming empty\n",
232 		       PCI_ARGS ( pci ), PCI_VPD_FIELD_ARGS ( field ) );
233 		len = 0;
234 	}
235 
236 	/* Initialise non-volatile options block */
237 	nvo_init ( nvo, &nvsvpd->nvs, field, len, nvs_vpd_nvo_resize, refcnt );
238 }
239