blob: 522421b1d43fd1bb4e8f938930eb6a455d48065b [file] [log] [blame]
Jamie Gennisaca51c02011-11-03 17:42:43 -07001/*
2 ** Copyright 2011, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17#include "egl_cache.h"
18#include "egl_display.h"
19#include "egl_impl.h"
20#include "egldefs.h"
21
Jamie Gennis98c63832011-11-07 17:03:54 -080022#include <fcntl.h>
23#include <sys/mman.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26#include <unistd.h>
27
Jamie Gennis76601082011-11-06 14:14:33 -080028// Cache size limits.
29static const size_t maxKeySize = 1024;
30static const size_t maxValueSize = 4096;
31static const size_t maxTotalSize = 64 * 1024;
32
Jamie Gennis98c63832011-11-07 17:03:54 -080033// Cache file header
34static const char* cacheFileMagic = "EGL$";
35static const size_t cacheFileHeaderSize = 8;
36
Jamie Gennis99c3d702011-11-08 17:59:36 -080037// The time in seconds to wait before saving newly inserted cache entries.
38static const unsigned int deferredSaveDelay = 4;
39
Jamie Gennisaca51c02011-11-03 17:42:43 -070040// ----------------------------------------------------------------------------
41namespace android {
42// ----------------------------------------------------------------------------
43
44#define BC_EXT_STR "EGL_ANDROID_blob_cache"
45
46//
Jamie Gennis76601082011-11-06 14:14:33 -080047// Callback functions passed to EGL.
Jamie Gennisaca51c02011-11-03 17:42:43 -070048//
49static void setBlob(const void* key, EGLsizei keySize, const void* value,
50 EGLsizei valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080051 egl_cache_t::get()->setBlob(key, keySize, value, valueSize);
Jamie Gennisaca51c02011-11-03 17:42:43 -070052}
53
54static EGLsizei getBlob(const void* key, EGLsizei keySize, void* value,
55 EGLsizei valueSize) {
Jamie Gennis76601082011-11-06 14:14:33 -080056 return egl_cache_t::get()->getBlob(key, keySize, value, valueSize);
57}
58
59//
60// egl_cache_t definition
61//
62egl_cache_t::egl_cache_t() :
63 mInitialized(false),
64 mBlobCache(NULL) {
65}
66
67egl_cache_t::~egl_cache_t() {
Jamie Gennisaca51c02011-11-03 17:42:43 -070068}
69
Jamie Gennis98c63832011-11-07 17:03:54 -080070egl_cache_t egl_cache_t::sCache;
71
Jamie Gennisaca51c02011-11-03 17:42:43 -070072egl_cache_t* egl_cache_t::get() {
Jamie Gennis98c63832011-11-07 17:03:54 -080073 return &sCache;
Jamie Gennisaca51c02011-11-03 17:42:43 -070074}
75
76void egl_cache_t::initialize(egl_display_t *display) {
Jamie Gennis76601082011-11-06 14:14:33 -080077 Mutex::Autolock lock(mMutex);
Jamie Gennisaca51c02011-11-03 17:42:43 -070078 for (int i = 0; i < IMPL_NUM_IMPLEMENTATIONS; i++) {
79 egl_connection_t* const cnx = &gEGLImpl[i];
80 if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
81 const char* exts = display->disp[i].queryString.extensions;
82 size_t bcExtLen = strlen(BC_EXT_STR);
83 size_t extsLen = strlen(exts);
84 bool equal = !strcmp(BC_EXT_STR, exts);
85 bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1);
86 bool atEnd = (bcExtLen+1) < extsLen &&
87 !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1));
88 bool inMiddle = strstr(" " BC_EXT_STR " ", exts);
89 if (equal || atStart || atEnd || inMiddle) {
90 PFNEGLSETBLOBCACHEFUNCSPROC eglSetBlobCacheFuncs;
91 eglSetBlobCacheFuncs =
92 reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSPROC>(
93 cnx->egl.eglGetProcAddress("eglSetBlobCacheFuncs"));
94 if (eglSetBlobCacheFuncs == NULL) {
95 LOGE("EGL_ANDROID_blob_cache advertised by display %d, "
96 "but unable to get eglSetBlobCacheFuncs", i);
97 continue;
98 }
99
Jamie Gennis76601082011-11-06 14:14:33 -0800100 eglSetBlobCacheFuncs(display->disp[i].dpy, android::setBlob,
101 android::getBlob);
Jamie Gennisaca51c02011-11-03 17:42:43 -0700102 EGLint err = cnx->egl.eglGetError();
103 if (err != EGL_SUCCESS) {
104 LOGE("eglSetBlobCacheFuncs resulted in an error: %#x",
105 err);
106 }
107 }
108 }
109 }
Jamie Gennis76601082011-11-06 14:14:33 -0800110 mInitialized = true;
111}
112
113void egl_cache_t::terminate() {
114 Mutex::Autolock lock(mMutex);
115 if (mBlobCache != NULL) {
116 saveBlobCacheLocked();
117 mBlobCache = NULL;
118 }
119 mInitialized = false;
120}
121
122void egl_cache_t::setBlob(const void* key, EGLsizei keySize, const void* value,
123 EGLsizei valueSize) {
124 Mutex::Autolock lock(mMutex);
125
126 if (keySize < 0 || valueSize < 0) {
127 LOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
128 return;
129 }
130
131 if (mInitialized) {
132 sp<BlobCache> bc = getBlobCacheLocked();
133 bc->set(key, keySize, value, valueSize);
Jamie Gennis99c3d702011-11-08 17:59:36 -0800134
135 if (!mSavePending) {
136 class DeferredSaveThread : public Thread {
137 public:
138 DeferredSaveThread() : Thread(false) {}
139
140 virtual bool threadLoop() {
141 sleep(deferredSaveDelay);
142 egl_cache_t* c = egl_cache_t::get();
143 Mutex::Autolock lock(c->mMutex);
144 if (c->mInitialized) {
145 c->saveBlobCacheLocked();
146 }
147 c->mSavePending = false;
148 return false;
149 }
150 };
151
152 // The thread will hold a strong ref to itself until it has finished
153 // running, so there's no need to keep a ref around.
154 sp<Thread> deferredSaveThread(new DeferredSaveThread());
155 mSavePending = true;
156 deferredSaveThread->run();
157 }
Jamie Gennis76601082011-11-06 14:14:33 -0800158 }
159}
160
161EGLsizei egl_cache_t::getBlob(const void* key, EGLsizei keySize, void* value,
162 EGLsizei valueSize) {
163 Mutex::Autolock lock(mMutex);
164
165 if (keySize < 0 || valueSize < 0) {
166 LOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed");
167 return 0;
168 }
169
170 if (mInitialized) {
171 sp<BlobCache> bc = getBlobCacheLocked();
172 return bc->get(key, keySize, value, valueSize);
173 }
174 return 0;
175}
176
Jamie Gennis98c63832011-11-07 17:03:54 -0800177void egl_cache_t::setCacheFilename(const char* filename) {
178 Mutex::Autolock lock(mMutex);
179 mFilename = filename;
180}
181
Jamie Gennis76601082011-11-06 14:14:33 -0800182sp<BlobCache> egl_cache_t::getBlobCacheLocked() {
183 if (mBlobCache == NULL) {
184 mBlobCache = new BlobCache(maxKeySize, maxValueSize, maxTotalSize);
185 loadBlobCacheLocked();
186 }
187 return mBlobCache;
188}
189
Jamie Gennis98c63832011-11-07 17:03:54 -0800190static uint32_t crc32c(const uint8_t* buf, size_t len) {
191 const uint32_t polyBits = 0x82F63B78;
192 uint32_t r = 0;
193 for (size_t i = 0; i < len; i++) {
194 r ^= buf[i];
195 for (int j = 0; j < 8; j++) {
196 if (r & 1) {
197 r = (r >> 1) ^ polyBits;
198 } else {
199 r >>= 1;
200 }
201 }
202 }
203 return r;
204}
205
Jamie Gennis76601082011-11-06 14:14:33 -0800206void egl_cache_t::saveBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800207 if (mFilename.length() > 0) {
208 size_t cacheSize = mBlobCache->getFlattenedSize();
209 size_t headerSize = cacheFileHeaderSize;
210 const char* fname = mFilename.string();
211
212 // Try to create the file with no permissions so we can write it
213 // without anyone trying to read it.
214 int fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
215 if (fd == -1) {
216 if (errno == EEXIST) {
217 // The file exists, delete it and try again.
218 if (unlink(fname) == -1) {
219 // No point in retrying if the unlink failed.
220 LOGE("error unlinking cache file %s: %s (%d)", fname,
221 strerror(errno), errno);
222 return;
223 }
224 // Retry now that we've unlinked the file.
225 fd = open(fname, O_CREAT | O_EXCL | O_RDWR, 0);
226 }
227 if (fd == -1) {
228 LOGE("error creating cache file %s: %s (%d)", fname,
229 strerror(errno), errno);
230 return;
231 }
232 }
233
234 size_t fileSize = headerSize + cacheSize;
235 if (ftruncate(fd, fileSize) == -1) {
236 LOGE("error setting cache file size: %s (%d)", strerror(errno),
237 errno);
238 close(fd);
239 unlink(fname);
240 return;
241 }
242
243 uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
244 PROT_WRITE, MAP_SHARED, fd, 0));
245 if (buf == MAP_FAILED) {
246 LOGE("error mmaping cache file: %s (%d)", strerror(errno),
247 errno);
248 close(fd);
249 unlink(fname);
250 return;
251 }
252
253 status_t err = mBlobCache->flatten(buf + headerSize, cacheSize, NULL,
254 0);
255 if (err != OK) {
256 LOGE("error writing cache contents: %s (%d)", strerror(-err),
257 -err);
258 munmap(buf, fileSize);
259 close(fd);
260 unlink(fname);
261 return;
262 }
263
264 // Write the file magic and CRC
265 memcpy(buf, cacheFileMagic, 4);
266 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
267 *crc = crc32c(buf + headerSize, cacheSize);
268
269 munmap(buf, fileSize);
270 fchmod(fd, S_IRUSR);
271 close(fd);
272 }
Jamie Gennis76601082011-11-06 14:14:33 -0800273}
274
275void egl_cache_t::loadBlobCacheLocked() {
Jamie Gennis98c63832011-11-07 17:03:54 -0800276 if (mFilename.length() > 0) {
277 size_t headerSize = cacheFileHeaderSize;
278
279 int fd = open(mFilename.string(), O_RDONLY, 0);
280 if (fd == -1) {
281 if (errno != ENOENT) {
282 LOGE("error opening cache file %s: %s (%d)", mFilename.string(),
283 strerror(errno), errno);
284 }
285 return;
286 }
287
288 struct stat statBuf;
289 if (fstat(fd, &statBuf) == -1) {
290 LOGE("error stat'ing cache file: %s (%d)", strerror(errno), errno);
291 close(fd);
292 return;
293 }
294
295 // Sanity check the size before trying to mmap it.
296 size_t fileSize = statBuf.st_size;
297 if (fileSize > maxTotalSize * 2) {
298 LOGE("cache file is too large: %#llx", statBuf.st_size);
299 close(fd);
300 return;
301 }
302
303 uint8_t* buf = reinterpret_cast<uint8_t*>(mmap(NULL, fileSize,
304 PROT_READ, MAP_PRIVATE, fd, 0));
305 if (buf == MAP_FAILED) {
306 LOGE("error mmaping cache file: %s (%d)", strerror(errno),
307 errno);
308 close(fd);
309 return;
310 }
311
312 // Check the file magic and CRC
313 size_t cacheSize = fileSize - headerSize;
314 if (memcmp(buf, cacheFileMagic, 4) != 0) {
315 LOGE("cache file has bad mojo");
316 close(fd);
317 return;
318 }
319 uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
320 if (crc32c(buf + headerSize, cacheSize) != *crc) {
321 LOGE("cache file failed CRC check");
322 close(fd);
323 return;
324 }
325
326 status_t err = mBlobCache->unflatten(buf + headerSize, cacheSize, NULL, 0);
327 if (err != OK) {
328 LOGE("error reading cache contents: %s (%d)", strerror(-err),
329 -err);
330 munmap(buf, fileSize);
331 close(fd);
332 return;
333 }
334
335 munmap(buf, fileSize);
336 close(fd);
337 }
Jamie Gennisaca51c02011-11-03 17:42:43 -0700338}
339
340// ----------------------------------------------------------------------------
341}; // namespace android
342// ----------------------------------------------------------------------------