/******************************************************************************* * Copyright (c) 2013 Linaro * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Linaro *******************************************************************************/ /* * Test for Android shared memory (ashmem) driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ANDROID /* In-tree compilation */ #include #else /* Freestanding compilation, use local copy */ #include "ashmem.h" #endif /* ANDROID */ #ifndef ASHMEM_MAJOR #define ASHMEM_MAJOR 10 /* misc device, should match MISC_DEVICE in linux/major.h */ #endif /* Stolen from the kernel */ #define DEFAULT_PROT_MASK (PROT_READ | PROT_WRITE | PROT_EXEC) #ifndef _LINUX_ASHMEM_H #ifndef _UAPI_LINUX_ASHMEM_H /* only if not defined already */ struct ashmem_pin { uint32_t offset; uint32_t len; }; #endif #endif /* current test run */ char testname[32]; /* device name */ char ashmemdev[64]; /* guess what */ size_t pagesize; void fatal (int error, const char *fmt, ...) { va_list ap; va_start (ap, fmt); vfprintf (stderr, fmt, ap); if (error) fprintf (stderr, " (error %d [%s])", error, strerror (error)); va_end (ap); fprintf (stderr, "\n"); printf ("[%s]: test failed\n", testname); exit (1); } int test_open (char *name, mode_t mode) { int fd; fd = open (name, mode); if (fd < 0) fatal (errno, "can't open %s", name); return fd; } int test_open_and_set_size (char *name, mode_t mode, size_t size) { int ret, fd = test_open (name, mode); ret = ioctl (fd, ASHMEM_SET_SIZE, size); if (ret < 0) fatal (errno, "can''t set size to %ld bytes\n", size); return fd; } void test_close (int fd) { if (close (fd) < 0) fatal (errno, "can't close fd %d", fd); } void ashmem_basic (void) { int fd; struct stat st; strcpy(testname, __func__); /* make sure we can open it */ fd = test_open (ashmemdev, O_RDWR); /* check whether it's a valid device file */ if (fstat (fd, &st) < 0) fatal (errno, "can't stat %s (fd %d)", ashmemdev, fd); if (!S_ISCHR (st.st_mode)) fatal (0, "%s is not a character device", ashmemdev); if (major (st.st_rdev) != ASHMEM_MAJOR) fatal (0, "%s is not a device with major %d", ashmemdev, ASHMEM_MAJOR); /* check that close works too */ test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_anonymous_region (void) { int fd, ret; char name[ASHMEM_NAME_LEN]; size_t size; strcpy(testname, __func__); /* test max possible size */ fd = test_open (ashmemdev, O_RDWR); size = (SIZE_MAX - pagesize - 1) & ~(pagesize - 1); ret = ioctl (fd, ASHMEM_SET_SIZE, size); if (ret < 0 && errno != ENOMEM) fatal (errno, "ENOMEM expected if too large size for ASHMEM_SET_SIZE is used"); test_close (fd); /* test whether normal anonymous region has the default name */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_GET_NAME, name); if (ret < 0) fatal (errno, "can't get region name"); if (strcmp (name, ASHMEM_NAME_DEF)) fatal (0, "expect %s, but got %s for anonymous region name", ASHMEM_NAME_DEF, name); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_named_region (void) { int fd, ret, i; char name[ASHMEM_NAME_LEN], name2[ASHMEM_NAME_LEN]; strcpy(testname, __func__); /* test invalid name pointer */ fd = test_open (ashmemdev, O_RDWR); ret = ioctl (fd, ASHMEM_SET_NAME, (char *) 0xdeadbeef); if (ret >= 0 || errno != EFAULT) fatal (errno, "ASHMEM_SET_NAME should not allow bad name pointer\n"); test_close (fd); /* test whether normal name matches between set and get */ snprintf (name, ASHMEM_NAME_LEN, "Test memory region"); fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_SET_NAME, name); if (ret < 0) fatal (errno, "can't set name '%s'", name); ret = ioctl (fd, ASHMEM_GET_NAME, name2); if (strcmp (name, name2)) fatal (0, "name mismatch: expect '%s', got '%s'", name, name2); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_pin_unpin (void) { int fd, ret; size_t size; void *mem; struct ashmem_pin pin; strcpy(testname, __func__); /* note the memory is pinned by default */ /* normal unpin/pin for the whole single-page region */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); mem = mmap (NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); pin.offset = 0, pin.len = pagesize; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret < 0) fatal (errno, "can't unpin region with offset %ld and size %ld", 0, pagesize); ret = ioctl (fd, ASHMEM_PIN, &pin); if (ret < 0) fatal (errno, "can't pin region with offset %ld and size %ld", 0, pagesize); if (munmap (mem, pagesize)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* make sure we can unpin zero bytes (means everything onward) */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); mem = mmap (NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); pin.offset = 0, pin.len = 0; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret < 0) fatal (errno, "should be able to unpin zero bytes"); if (munmap (mem, pagesize)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* make sure we can't unpin across page boundaries */ size = pagesize * 16; fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); pin.offset = pagesize + 7, pin.len = 3 * pagesize + 11; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret >= 0) fatal (errno, "should not be able to unpin across page boundaries"); if (munmap (mem, size)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* make sure we can't unpin more than requested */ size = pagesize * 8; fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); pin.offset = 0, pin.len = size * 2; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret >= 0) fatal (errno, "should not be able to unpin more than allocated"); if (munmap (mem, size)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* make sure we can't unpin beyond the region */ size = pagesize * 8; fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); pin.offset = size * 2, pin.len = pagesize; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret >= 0) fatal (errno, "should not be able to unpin beyond the region"); if (munmap (mem, size)) fatal (errno, "can't unmap ashmem region"); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_pin_status (void) { int fd, ret; void *mem; struct ashmem_pin pin; size_t size = pagesize * 4; strcpy(testname, __func__); /* get 4-pages region */ fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); /* make sure we can examine zero-sized region (means everything onward) */ pin.offset = 0, pin.len = 0; ret = ioctl (fd, ASHMEM_GET_PIN_STATUS, &pin); if (ret < 0) fatal (errno, "should be able to ask status of zero-sized region"); /* make sure we can't examine across page boundaries */ pin.offset = pagesize + 11, pin.len = pagesize + 17; ret = ioctl (fd, ASHMEM_GET_PIN_STATUS, &pin); if (ret >= 0) fatal (errno, "should not be able to ask across page boundaries"); /* make sure we can't examine beyond the region */ pin.offset = size * 2, pin.len = pagesize; ret = ioctl (fd, ASHMEM_GET_PIN_STATUS, &pin); if (ret >= 0) fatal (errno, "should not be able to ask beyond the region"); /* make sure it's initially pinned */ pin.offset = 0, pin.len = size; ret = ioctl (fd, ASHMEM_GET_PIN_STATUS, &pin); if (ret != ASHMEM_IS_PINNED) fatal (0, "expect initially pinned region"); /* unpin it */ pin.offset = 0, pin.len = size; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret < 0) fatal (errno, "can't unpin region"); /* make sure it's not pinned now */ pin.offset = 0, pin.len = size; ret = ioctl (fd, ASHMEM_GET_PIN_STATUS, &pin); if (ret != ASHMEM_IS_UNPINNED) fatal (0, "expect unpinned region"); if (munmap (mem, size)) fatal (errno, "can't unmap ashmem region\n"); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_size (void) { int fd, ret; strcpy(testname, __func__); /* check whether set and get sizes match */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_GET_SIZE, 0); if (ret != pagesize) fatal (0, "size mismatch: expect %ld, got %d", pagesize, ret); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_mmap (void) { void *mem; int i, fd, ret; size_t size = pagesize * 4; char *pattern = alloca (size); strcpy(testname, __func__); /* fill the random pattern */ for (i = 0; i < size; i++) pattern[i] = 'a' + rand () % ('z' - 'a'); /* write the pattern to ashmem region, then compare with origin */ fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); memcpy (mem, pattern, size); if (memcmp (mem, pattern, size)) fatal (0, "pattern vs. ashmem mismatch"); if (munmap (mem, size)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* make sure we can mmap a half in the middle */ fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size / 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, pagesize); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region in the middle"); if (munmap (mem, size / 2)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* make sure we can't mmap read-only region for read/write */ fd = test_open_and_set_size (ashmemdev, O_RDONLY, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem != MAP_FAILED || errno != EACCES) fatal (errno, "should not allow read-write map for read-only region"); test_close (fd); /* make sure we can't mmap write-only region for read/write */ fd = test_open_and_set_size (ashmemdev, O_WRONLY, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem != MAP_FAILED || errno != EACCES) fatal (errno, "should not allow read-write map for write-only region"); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_prot (void) { void *mem; int fd, ret; strcpy(testname, __func__); /* check whether default protection is read/write/exec */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_GET_PROT_MASK); if (ret != DEFAULT_PROT_MASK) fatal (0, "default protection mask mismatch (got %x expect %x)", ret, DEFAULT_PROT_MASK); test_close (fd); /* check whether we can remove some protection bits */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_GET_PROT_MASK); if (ret != DEFAULT_PROT_MASK) fatal (errno, "can't get protection mask or unexpected mask"); ret &= ~(PROT_READ | PROT_WRITE); ret = ioctl (fd, ASHMEM_SET_PROT_MASK, ret); if (ret < 0) fatal (errno, "can't remove protection bits"); ret = ioctl (fd, ASHMEM_GET_PROT_MASK); if (ret != PROT_EXEC) fatal (errno, "protection mask mismatch (expect %x got %x)", PROT_EXEC, ret); test_close (fd); /* check whether we can't map read/write after removing PROT_READ... */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_GET_PROT_MASK); if (ret != DEFAULT_PROT_MASK) fatal (errno, "can't get protection mask or unexpected mask"); ret &= ~PROT_READ; ret = ioctl (fd, ASHMEM_SET_PROT_MASK, ret); if (ret < 0) fatal (errno, "can't remove protection bits"); mem = mmap (NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem != MAP_FAILED || errno != EPERM) fatal (errno, "should not allow read/write map after removing PROT_READ"); /* ...but can map write-only */ mem = mmap (NULL, pagesize, PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "should allow write-only map after removing PROT_READ"); if (munmap (mem, pagesize)) fatal (errno, "can't unmap ashmem region"); test_close (fd); /* likewise after removing PROT_WRITE... */ fd = test_open_and_set_size (ashmemdev, O_RDWR, pagesize); ret = ioctl (fd, ASHMEM_GET_PROT_MASK); if (ret != DEFAULT_PROT_MASK) fatal (errno, "can't get protection mask or unexpected mask"); ret &= ~PROT_WRITE; ret = ioctl (fd, ASHMEM_SET_PROT_MASK, ret); if (ret < 0) fatal (errno, "can't remove protection bits"); mem = mmap (NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem != MAP_FAILED || errno != EPERM) fatal (errno, "should not allow read/write map after removing PROT_WRITE"); /* ...still can map read-only */ mem = mmap (NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "should allow read-only map after removing PROT_WRITE"); if (munmap (mem, pagesize)) fatal (errno, "can't unmap ashmem region"); test_close (fd); printf ("[%s]: test passed\n", __func__); } void ashmem_purge (void) { void *mem; struct ashmem_pin pin; const int count = 16; int i, fd, ret, size = pagesize * count; strcpy(testname, __func__); /* allocate 16 pages */ fd = test_open_and_set_size (ashmemdev, O_RDWR, size); mem = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) fatal (errno, "can't map ashmem region"); /* unpin all odd pages */ for (i = 1; i < count; i += 2) { pin.offset = i * pagesize; pin.len = pagesize; ret = ioctl (fd, ASHMEM_UNPIN, &pin); if (ret < 0) fatal (errno, "can't unpin page %d of %d", i, count); } /* purge should release 8 pages */ ret = ioctl (fd, ASHMEM_PURGE_ALL_CACHES); if (ret != count / 2) { if (ret < 0) fatal (errno, "can't purge caches"); else if (ret < count / 2) fatal (0, "purge %d pages, but expect %d", ret, count / 2); else printf("[%s]: warning: purged more pages (%d) than expected (%d), probably somebody else does the same....", __func__, ret, count / 2); } if (munmap (mem, size)) fatal (errno, "can't unmap ashmem region"); test_close (fd); printf ("[%s]: test passed\n", __func__); } int main(int argc, char *argv[]) { snprintf (ashmemdev, sizeof (ashmemdev), "/%s", ASHMEM_NAME_DEF); pagesize = getpagesize (); ashmem_basic (); ashmem_anonymous_region (); ashmem_named_region (); ashmem_pin_unpin (); ashmem_pin_status (); ashmem_size (); ashmem_mmap (); ashmem_prot (); ashmem_purge (); return 0; }