| // Copyright 2006 Google Inc. All Rights Reserved. |
| // Author: nsanders, menderico |
| |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // os.cc : os and machine specific implementation |
| // This file includes an abstracted interface |
| // for linux-distro specific and HW specific |
| // interfaces. |
| |
| #include "os.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/types.h> |
| #include <malloc.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/ioctl.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/ipc.h> |
| #ifdef HAVE_SYS_SHM_H |
| #include <sys/shm.h> |
| #endif |
| #include <unistd.h> |
| |
| #ifndef SHM_HUGETLB |
| #define SHM_HUGETLB 04000 // remove when glibc defines it |
| #endif |
| |
| #include <string> |
| #include <list> |
| |
| // This file must work with autoconf on its public version, |
| // so these includes are correct. |
| #include "sattypes.h" |
| #include "error_diag.h" |
| #include "clock.h" |
| |
| // OsLayer initialization. |
| OsLayer::OsLayer() { |
| testmem_ = 0; |
| testmemsize_ = 0; |
| totalmemsize_ = 0; |
| min_hugepages_bytes_ = 0; |
| reserve_mb_ = 0; |
| normal_mem_ = true; |
| use_hugepages_ = false; |
| use_posix_shm_ = false; |
| dynamic_mapped_shmem_ = false; |
| mmapped_allocation_ = false; |
| shmid_ = 0; |
| |
| time_initialized_ = 0; |
| |
| regionsize_ = 0; |
| regioncount_ = 1; |
| num_cpus_ = 0; |
| num_nodes_ = 0; |
| num_cpus_per_node_ = 0; |
| error_diagnoser_ = 0; |
| err_log_callback_ = 0; |
| error_injection_ = false; |
| |
| void *pvoid = 0; |
| address_mode_ = sizeof(pvoid) * 8; |
| |
| has_clflush_ = false; |
| has_vector_ = false; |
| |
| use_flush_page_cache_ = false; |
| |
| clock_ = NULL; |
| } |
| |
| // OsLayer cleanup. |
| OsLayer::~OsLayer() { |
| if (error_diagnoser_) |
| delete error_diagnoser_; |
| if (clock_) |
| delete clock_; |
| } |
| |
| // OsLayer initialization. |
| bool OsLayer::Initialize() { |
| if (!clock_) { |
| clock_ = new Clock(); |
| } |
| |
| time_initialized_ = clock_->Now(); |
| // Detect asm support. |
| GetFeatures(); |
| |
| if (num_cpus_ == 0) { |
| num_nodes_ = 1; |
| num_cpus_ = sysconf(_SC_NPROCESSORS_ONLN); |
| num_cpus_per_node_ = num_cpus_ / num_nodes_; |
| } |
| logprintf(5, "Log: %d nodes, %d cpus.\n", num_nodes_, num_cpus_); |
| sat_assert(CPU_SETSIZE >= num_cpus_); |
| cpu_sets_.resize(num_nodes_); |
| cpu_sets_valid_.resize(num_nodes_); |
| // Create error diagnoser. |
| error_diagnoser_ = new ErrorDiag(); |
| if (!error_diagnoser_->set_os(this)) |
| return false; |
| return true; |
| } |
| |
| // Machine type detected. Can we implement all these functions correctly? |
| bool OsLayer::IsSupported() { |
| if (kOpenSource) { |
| // There are no explicitly supported systems in open source version. |
| return true; |
| } |
| |
| // This is the default empty implementation. |
| // SAT won't report full error information. |
| return false; |
| } |
| |
| int OsLayer::AddressMode() { |
| // Detect 32/64 bit binary. |
| void *pvoid = 0; |
| return sizeof(pvoid) * 8; |
| } |
| |
| // Translates user virtual to physical address. |
| uint64 OsLayer::VirtualToPhysical(void *vaddr) { |
| uint64 frame, shift; |
| off64_t off = ((uintptr_t)vaddr) / sysconf(_SC_PAGESIZE) * 8; |
| int fd = open(kPagemapPath, O_RDONLY); |
| // /proc/self/pagemap is available in kernel >= 2.6.25 |
| if (fd < 0) |
| return 0; |
| |
| if (lseek64(fd, off, SEEK_SET) != off || read(fd, &frame, 8) != 8) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(0, "Process Error: failed to access %s with errno %d (%s)\n", |
| kPagemapPath, err, errtxt.c_str()); |
| if (fd >= 0) |
| close(fd); |
| return 0; |
| } |
| close(fd); |
| if (!(frame & (1LL << 63)) || (frame & (1LL << 62))) |
| return 0; |
| shift = (frame >> 55) & 0x3f; |
| frame = (frame & 0x007fffffffffffffLL) << shift; |
| return frame | ((uintptr_t)vaddr & ((1LL << shift) - 1)); |
| } |
| |
| // Returns the HD device that contains this file. |
| string OsLayer::FindFileDevice(string filename) { |
| return "hdUnknown"; |
| } |
| |
| // Returns a list of locations corresponding to HD devices. |
| list<string> OsLayer::FindFileDevices() { |
| // No autodetection on unknown systems. |
| list<string> locations; |
| return locations; |
| } |
| |
| |
| // Get HW core features from cpuid instruction. |
| void OsLayer::GetFeatures() { |
| #if defined(STRESSAPPTEST_CPU_X86_64) || defined(STRESSAPPTEST_CPU_I686) |
| unsigned int eax = 1, ebx, ecx, edx; |
| cpuid(&eax, &ebx, &ecx, &edx); |
| has_clflush_ = (edx >> 19) & 1; |
| has_vector_ = (edx >> 26) & 1; // SSE2 caps bit. |
| |
| logprintf(9, "Log: has clflush: %s, has sse2: %s\n", |
| has_clflush_ ? "true" : "false", |
| has_vector_ ? "true" : "false"); |
| #elif defined(STRESSAPPTEST_CPU_PPC) |
| // All PPC implementations have cache flush instructions. |
| has_clflush_ = true; |
| #elif defined(STRESSAPPTEST_CPU_ARMV7A) |
| // TODO(nsanders): add detect from /proc/cpuinfo or /proc/self/auxv. |
| // For now assume neon and don't run -W if you don't have it. |
| has_vector_ = true; // NEON. |
| #warning "Unsupported CPU type ARMV7A: unable to determine feature set." |
| #else |
| #warning "Unsupported CPU type: unable to determine feature set." |
| #endif |
| } |
| |
| |
| // Enable FlushPageCache to be functional instead of a NOP. |
| void OsLayer::ActivateFlushPageCache(void) { |
| logprintf(9, "Log: page cache will be flushed as needed\n"); |
| use_flush_page_cache_ = true; |
| } |
| |
| // Flush the page cache to ensure reads come from the disk. |
| bool OsLayer::FlushPageCache(void) { |
| if (!use_flush_page_cache_) |
| return true; |
| |
| // First, ask the kernel to write the cache to the disk. |
| sync(); |
| |
| // Second, ask the kernel to empty the cache by writing "1" to |
| // "/proc/sys/vm/drop_caches". |
| static const char *drop_caches_file = "/proc/sys/vm/drop_caches"; |
| int dcfile = open(drop_caches_file, O_WRONLY); |
| if (dcfile < 0) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(3, "Log: failed to open %s - err %d (%s)\n", |
| drop_caches_file, err, errtxt.c_str()); |
| return false; |
| } |
| |
| ssize_t bytes_written = write(dcfile, "1", 1); |
| close(dcfile); |
| |
| if (bytes_written != 1) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(3, "Log: failed to write %s - err %d (%s)\n", |
| drop_caches_file, err, errtxt.c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| |
| // We need to flush the cacheline here. |
| void OsLayer::Flush(void *vaddr) { |
| // Use the generic flush. This function is just so we can override |
| // this if we are so inclined. |
| if (has_clflush_) { |
| OsLayer::FastFlush(vaddr); |
| } |
| } |
| |
| |
| // Run C or ASM copy as appropriate.. |
| bool OsLayer::AdlerMemcpyWarm(uint64 *dstmem, uint64 *srcmem, |
| unsigned int size_in_bytes, |
| AdlerChecksum *checksum) { |
| if (has_vector_) { |
| return AdlerMemcpyAsm(dstmem, srcmem, size_in_bytes, checksum); |
| } else { |
| return AdlerMemcpyWarmC(dstmem, srcmem, size_in_bytes, checksum); |
| } |
| } |
| |
| |
| // Translate physical address to memory module/chip name. |
| // Assumes interleaving between two memory channels based on the XOR of |
| // all address bits in the 'channel_hash' mask, with repeated 'channel_width_' |
| // blocks with bits distributed from each chip in that channel. |
| int OsLayer::FindDimm(uint64 addr, char *buf, int len) { |
| if (!channels_) { |
| snprintf(buf, len, "DIMM Unknown"); |
| return -1; |
| } |
| |
| // Find channel by XORing address bits in channel_hash mask. |
| uint32 low = static_cast<uint32>(addr & channel_hash_); |
| uint32 high = static_cast<uint32>((addr & channel_hash_) >> 32); |
| vector<string>& channel = (*channels_)[ |
| __builtin_parity(high) ^ __builtin_parity(low)]; |
| |
| // Find dram chip by finding which byte within the channel |
| // by address mod channel width, then divide the channel |
| // evenly among the listed dram chips. Note, this will not work |
| // with x4 dram. |
| int chip = (addr % (channel_width_ / 8)) / |
| ((channel_width_ / 8) / channel.size()); |
| string name = channel[chip]; |
| snprintf(buf, len, "%s", name.c_str()); |
| return 1; |
| } |
| |
| |
| // Classifies addresses according to "regions" |
| // This isn't really implemented meaningfully here.. |
| int32 OsLayer::FindRegion(uint64 addr) { |
| static bool warned = false; |
| |
| if (regionsize_ == 0) { |
| regionsize_ = totalmemsize_ / 8; |
| if (regionsize_ < 512 * kMegabyte) |
| regionsize_ = 512 * kMegabyte; |
| regioncount_ = totalmemsize_ / regionsize_; |
| if (regioncount_ < 1) regioncount_ = 1; |
| } |
| |
| int32 region_num = addr / regionsize_; |
| if (region_num >= regioncount_) { |
| if (!warned) { |
| logprintf(0, "Log: region number %d exceeds region count %d\n", |
| region_num, regioncount_); |
| warned = true; |
| } |
| region_num = region_num % regioncount_; |
| } |
| return region_num; |
| } |
| |
| // Report which cores are associated with a given region. |
| cpu_set_t *OsLayer::FindCoreMask(int32 region) { |
| sat_assert(region >= 0); |
| region %= num_nodes_; |
| if (!cpu_sets_valid_[region]) { |
| CPU_ZERO(&cpu_sets_[region]); |
| for (int i = 0; i < num_cpus_per_node_; ++i) { |
| CPU_SET(i + region * num_cpus_per_node_, &cpu_sets_[region]); |
| } |
| cpu_sets_valid_[region] = true; |
| logprintf(5, "Log: Region %d mask 0x%s\n", |
| region, FindCoreMaskFormat(region).c_str()); |
| } |
| return &cpu_sets_[region]; |
| } |
| |
| // Return cores associated with a given region in hex string. |
| string OsLayer::FindCoreMaskFormat(int32 region) { |
| cpu_set_t* mask = FindCoreMask(region); |
| string format = cpuset_format(mask); |
| if (format.size() < 8) |
| format = string(8 - format.size(), '0') + format; |
| return format; |
| } |
| |
| // Report an error in an easily parseable way. |
| bool OsLayer::ErrorReport(const char *part, const char *symptom, int count) { |
| time_t now = clock_->Now(); |
| int ttf = now - time_initialized_; |
| if (strlen(symptom) && strlen(part)) { |
| logprintf(0, "Report Error: %s : %s : %d : %ds\n", |
| symptom, part, count, ttf); |
| } else { |
| // Log something so the error still shows up, but this won't break the |
| // parser. |
| logprintf(0, "Warning: Invalid Report Error: " |
| "%s : %s : %d : %ds\n", symptom, part, count, ttf); |
| } |
| return true; |
| } |
| |
| // Read the number of hugepages out of the kernel interface in proc. |
| int64 OsLayer::FindHugePages() { |
| char buf[65] = "0"; |
| |
| // This is a kernel interface to query the numebr of hugepages |
| // available in the system. |
| static const char *hugepages_info_file = "/proc/sys/vm/nr_hugepages"; |
| int hpfile = open(hugepages_info_file, O_RDONLY); |
| |
| ssize_t bytes_read = read(hpfile, buf, 64); |
| close(hpfile); |
| |
| if (bytes_read <= 0) { |
| logprintf(12, "Log: /proc/sys/vm/nr_hugepages " |
| "read did not provide data\n"); |
| return 0; |
| } |
| |
| if (bytes_read == 64) { |
| logprintf(0, "Process Error: /proc/sys/vm/nr_hugepages " |
| "is surprisingly large\n"); |
| return 0; |
| } |
| |
| // Add a null termintation to be string safe. |
| buf[bytes_read] = '\0'; |
| // Read the page count. |
| int64 pages = strtoull(buf, NULL, 10); // NOLINT |
| |
| return pages; |
| } |
| |
| int64 OsLayer::FindFreeMemSize() { |
| int64 size = 0; |
| int64 minsize = 0; |
| if (totalmemsize_ > 0) |
| return totalmemsize_; |
| |
| int64 pages = sysconf(_SC_PHYS_PAGES); |
| int64 avpages = sysconf(_SC_AVPHYS_PAGES); |
| int64 pagesize = sysconf(_SC_PAGESIZE); |
| int64 physsize = pages * pagesize; |
| int64 avphyssize = avpages * pagesize; |
| |
| // Assume 2MB hugepages. |
| int64 hugepagesize = FindHugePages() * 2 * kMegabyte; |
| |
| if ((pages == -1) || (pagesize == -1)) { |
| logprintf(0, "Process Error: sysconf could not determine memory size.\n"); |
| return 0; |
| } |
| |
| // We want to leave enough stuff for things to run. |
| // If the user specified a minimum amount of memory to expect, require that. |
| // Otherwise, if more than 2GB is present, leave 192M + 5% for other stuff. |
| // If less than 2GB is present use 85% of what's available. |
| // These are fairly arbitrary numbers that seem to work OK. |
| // |
| // TODO(nsanders): is there a more correct way to determine target |
| // memory size? |
| if (hugepagesize > 0) { |
| if (min_hugepages_bytes_ > 0) { |
| minsize = min_hugepages_bytes_; |
| } else { |
| minsize = hugepagesize; |
| } |
| } else { |
| if (physsize < 2048LL * kMegabyte) { |
| minsize = ((pages * 85) / 100) * pagesize; |
| } else { |
| minsize = ((pages * 95) / 100) * pagesize - (192 * kMegabyte); |
| } |
| // Make sure that at least reserve_mb_ is left for the system. |
| if (reserve_mb_ > 0) { |
| int64 totalsize = pages * pagesize; |
| int64 reserve_kb = reserve_mb_ * kMegabyte; |
| if (reserve_kb > totalsize) { |
| logprintf(0, "Procedural Error: %lld is bigger than the total memory " |
| "available %lld\n", reserve_kb, totalsize); |
| } else if (reserve_kb > totalsize - minsize) { |
| logprintf(5, "Warning: Overriding memory to use: original %lld, " |
| "current %lld\n", minsize, totalsize - reserve_kb); |
| minsize = totalsize - reserve_kb; |
| } |
| } |
| } |
| |
| // Use hugepage sizing if available. |
| if (hugepagesize > 0) { |
| if (hugepagesize < minsize) { |
| logprintf(0, "Procedural Error: Not enough hugepages. " |
| "%lldMB available < %lldMB required.\n", |
| hugepagesize / kMegabyte, |
| minsize / kMegabyte); |
| // Require the calculated minimum amount of memory. |
| size = minsize; |
| } else { |
| // Require that we get all hugepages. |
| size = hugepagesize; |
| } |
| } else { |
| // Require the calculated minimum amount of memory. |
| size = minsize; |
| } |
| |
| logprintf(5, "Log: Total %lld MB. Free %lld MB. Hugepages %lld MB. " |
| "Targeting %lld MB (%lld%%)\n", |
| physsize / kMegabyte, |
| avphyssize / kMegabyte, |
| hugepagesize / kMegabyte, |
| size / kMegabyte, |
| size * 100 / physsize); |
| |
| totalmemsize_ = size; |
| return size; |
| } |
| |
| // Allocates all memory available. |
| int64 OsLayer::AllocateAllMem() { |
| int64 length = FindFreeMemSize(); |
| bool retval = AllocateTestMem(length, 0); |
| if (retval) |
| return length; |
| else |
| return 0; |
| } |
| |
| // Allocate the target memory. This may be from malloc, hugepage pool |
| // or other platform specific sources. |
| bool OsLayer::AllocateTestMem(int64 length, uint64 paddr_base) { |
| // Try hugepages first. |
| void *buf = 0; |
| |
| sat_assert(length >= 0); |
| |
| if (paddr_base) |
| logprintf(0, "Process Error: non zero paddr_base %#llx is not supported," |
| " ignore.\n", paddr_base); |
| |
| // Determine optimal memory allocation path. |
| bool prefer_hugepages = false; |
| bool prefer_posix_shm = false; |
| bool prefer_dynamic_mapping = false; |
| |
| // Are there enough hugepages? |
| int64 hugepagesize = FindHugePages() * 2 * kMegabyte; |
| // TODO(nsanders): Is there enough /dev/shm? Is there enough free memeory? |
| if ((length >= 1400LL * kMegabyte) && (address_mode_ == 32)) { |
| prefer_dynamic_mapping = true; |
| prefer_posix_shm = true; |
| logprintf(3, "Log: Prefer POSIX shared memory allocation.\n"); |
| logprintf(3, "Log: You may need to run " |
| "'sudo mount -o remount,size=100\% /dev/shm.'\n"); |
| } else if (hugepagesize >= length) { |
| prefer_hugepages = true; |
| logprintf(3, "Log: Prefer using hugepage allocation.\n"); |
| } else { |
| logprintf(3, "Log: Prefer plain malloc memory allocation.\n"); |
| } |
| |
| #ifdef HAVE_SYS_SHM_H |
| // Allocate hugepage mapped memory. |
| if (prefer_hugepages) { |
| do { // Allow break statement. |
| int shmid; |
| void *shmaddr; |
| |
| if ((shmid = shmget(2, length, |
| SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W)) < 0) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(3, "Log: failed to allocate shared hugepage " |
| "object - err %d (%s)\n", |
| err, errtxt.c_str()); |
| logprintf(3, "Log: sysctl -w vm.nr_hugepages=XXX allows hugepages.\n"); |
| break; |
| } |
| |
| shmaddr = shmat(shmid, NULL, 0); |
| if (shmaddr == reinterpret_cast<void*>(-1)) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(0, "Log: failed to attach shared " |
| "hugepage object - err %d (%s).\n", |
| err, errtxt.c_str()); |
| if (shmctl(shmid, IPC_RMID, NULL) < 0) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(0, "Log: failed to remove shared " |
| "hugepage object - err %d (%s).\n", |
| err, errtxt.c_str()); |
| } |
| break; |
| } |
| use_hugepages_ = true; |
| shmid_ = shmid; |
| buf = shmaddr; |
| logprintf(0, "Log: Using shared hugepage object 0x%x at %p.\n", |
| shmid, shmaddr); |
| } while (0); |
| } |
| |
| if ((!use_hugepages_) && prefer_posix_shm) { |
| do { |
| int shm_object; |
| void *shmaddr = NULL; |
| |
| shm_object = shm_open("/stressapptest", O_CREAT | O_RDWR, S_IRWXU); |
| if (shm_object < 0) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(3, "Log: failed to allocate shared " |
| "smallpage object - err %d (%s)\n", |
| err, errtxt.c_str()); |
| break; |
| } |
| |
| if (0 > ftruncate(shm_object, length)) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(3, "Log: failed to ftruncate shared " |
| "smallpage object - err %d (%s)\n", |
| err, errtxt.c_str()); |
| break; |
| } |
| |
| // 32 bit linux apps can only use ~1.4G of address space. |
| // Use dynamic mapping for allocations larger than that. |
| // Currently perf hit is ~10% for this. |
| if (prefer_dynamic_mapping) { |
| dynamic_mapped_shmem_ = true; |
| } else { |
| // Do a full mapping here otherwise. |
| shmaddr = mmap64(NULL, length, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_NORESERVE | MAP_LOCKED | MAP_POPULATE, |
| shm_object, 0); |
| if (shmaddr == reinterpret_cast<void*>(-1)) { |
| int err = errno; |
| string errtxt = ErrorString(err); |
| logprintf(0, "Log: failed to map shared " |
| "smallpage object - err %d (%s).\n", |
| err, errtxt.c_str()); |
| break; |
| } |
| } |
| |
| use_posix_shm_ = true; |
| shmid_ = shm_object; |
| buf = shmaddr; |
| char location_message[256] = ""; |
| if (dynamic_mapped_shmem_) { |
| sprintf(location_message, "mapped as needed"); |
| } else { |
| sprintf(location_message, "at %p", shmaddr); |
| } |
| logprintf(0, "Log: Using posix shared memory object 0x%x %s.\n", |
| shm_object, location_message); |
| } while (0); |
| shm_unlink("/stressapptest"); |
| } |
| #endif // HAVE_SYS_SHM_H |
| |
| if (!use_hugepages_ && !use_posix_shm_) { |
| // If the page size is what SAT is expecting explicitly perform mmap() |
| // allocation. |
| if (sysconf(_SC_PAGESIZE) >= 4096) { |
| void *map_buf = mmap(NULL, length, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (map_buf != MAP_FAILED) { |
| buf = map_buf; |
| mmapped_allocation_ = true; |
| logprintf(0, "Log: Using mmap() allocation at %p.\n", buf); |
| } |
| } |
| if (!mmapped_allocation_) { |
| // Use memalign to ensure that blocks are aligned enough for disk direct |
| // IO. |
| buf = static_cast<char*>(memalign(4096, length)); |
| if (buf) { |
| logprintf(0, "Log: Using memaligned allocation at %p.\n", buf); |
| } else { |
| logprintf(0, "Process Error: memalign returned 0\n"); |
| if ((length >= 1499LL * kMegabyte) && (address_mode_ == 32)) { |
| logprintf(0, "Log: You are trying to allocate > 1.4G on a 32 " |
| "bit process. Please setup shared memory.\n"); |
| } |
| } |
| } |
| } |
| |
| testmem_ = buf; |
| if (buf || dynamic_mapped_shmem_) { |
| testmemsize_ = length; |
| } else { |
| testmemsize_ = 0; |
| } |
| |
| return (buf != 0) || dynamic_mapped_shmem_; |
| } |
| |
| // Free the test memory. |
| void OsLayer::FreeTestMem() { |
| if (testmem_) { |
| if (use_hugepages_) { |
| #ifdef HAVE_SYS_SHM_H |
| shmdt(testmem_); |
| shmctl(shmid_, IPC_RMID, NULL); |
| #endif |
| } else if (use_posix_shm_) { |
| if (!dynamic_mapped_shmem_) { |
| munmap(testmem_, testmemsize_); |
| } |
| close(shmid_); |
| } else if (mmapped_allocation_) { |
| munmap(testmem_, testmemsize_); |
| } else { |
| free(testmem_); |
| } |
| testmem_ = 0; |
| testmemsize_ = 0; |
| } |
| } |
| |
| |
| // Prepare the target memory. It may requre mapping in, or this may be a noop. |
| void *OsLayer::PrepareTestMem(uint64 offset, uint64 length) { |
| sat_assert((offset + length) <= testmemsize_); |
| if (dynamic_mapped_shmem_) { |
| // TODO(nsanders): Check if we can support MAP_NONBLOCK, |
| // and evaluate performance hit from not using it. |
| #ifdef HAVE_MMAP64 |
| void * mapping = mmap64(NULL, length, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_NORESERVE | MAP_LOCKED | MAP_POPULATE, |
| shmid_, offset); |
| #else |
| void * mapping = mmap(NULL, length, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_NORESERVE | MAP_LOCKED | MAP_POPULATE, |
| shmid_, offset); |
| #endif |
| if (mapping == MAP_FAILED) { |
| string errtxt = ErrorString(errno); |
| logprintf(0, "Process Error: PrepareTestMem mmap64(%llx, %llx) failed. " |
| "error: %s.\n", |
| offset, length, errtxt.c_str()); |
| sat_assert(0); |
| } |
| return mapping; |
| } |
| |
| return reinterpret_cast<void*>(reinterpret_cast<char*>(testmem_) + offset); |
| } |
| |
| // Release the test memory resources, if any. |
| void OsLayer::ReleaseTestMem(void *addr, uint64 offset, uint64 length) { |
| if (dynamic_mapped_shmem_) { |
| int retval = munmap(addr, length); |
| if (retval == -1) { |
| string errtxt = ErrorString(errno); |
| logprintf(0, "Process Error: ReleaseTestMem munmap(%p, %llx) failed. " |
| "error: %s.\n", |
| addr, length, errtxt.c_str()); |
| sat_assert(0); |
| } |
| } |
| } |
| |
| // No error polling on unknown systems. |
| int OsLayer::ErrorPoll() { |
| return 0; |
| } |
| |
| // Generally, poll for errors once per second. |
| void OsLayer::ErrorWait() { |
| sat_sleep(1); |
| return; |
| } |
| |
| // Open a PCI bus-dev-func as a file and return its file descriptor. |
| // Error is indicated by return value less than zero. |
| int OsLayer::PciOpen(int bus, int device, int function) { |
| char dev_file[256]; |
| |
| snprintf(dev_file, sizeof(dev_file), "/proc/bus/pci/%02x/%02x.%x", |
| bus, device, function); |
| |
| int fd = open(dev_file, O_RDWR); |
| if (fd == -1) { |
| logprintf(0, "Process Error: Unable to open PCI bus %d, device %d, " |
| "function %d (errno %d).\n", |
| bus, device, function, errno); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| |
| // Read and write functions to access PCI config. |
| uint32 OsLayer::PciRead(int fd, uint32 offset, int width) { |
| // Strict aliasing rules lawyers will cause data corruption |
| // on cast pointers in some gccs. |
| union { |
| uint32 l32; |
| uint16 l16; |
| uint8 l8; |
| } datacast; |
| datacast.l32 = 0; |
| uint32 size = width / 8; |
| |
| sat_assert((width == 32) || (width == 16) || (width == 8)); |
| sat_assert(offset <= (256 - size)); |
| |
| if (lseek(fd, offset, SEEK_SET) < 0) { |
| logprintf(0, "Process Error: Can't seek %x\n", offset); |
| return 0; |
| } |
| if (read(fd, &datacast, size) != static_cast<ssize_t>(size)) { |
| logprintf(0, "Process Error: Can't read %x\n", offset); |
| return 0; |
| } |
| |
| // Extract the data. |
| switch (width) { |
| case 8: |
| sat_assert(&(datacast.l8) == reinterpret_cast<uint8*>(&datacast)); |
| return datacast.l8; |
| case 16: |
| sat_assert(&(datacast.l16) == reinterpret_cast<uint16*>(&datacast)); |
| return datacast.l16; |
| case 32: |
| return datacast.l32; |
| } |
| return 0; |
| } |
| |
| void OsLayer::PciWrite(int fd, uint32 offset, uint32 value, int width) { |
| // Strict aliasing rules lawyers will cause data corruption |
| // on cast pointers in some gccs. |
| union { |
| uint32 l32; |
| uint16 l16; |
| uint8 l8; |
| } datacast; |
| datacast.l32 = 0; |
| uint32 size = width / 8; |
| |
| sat_assert((width == 32) || (width == 16) || (width == 8)); |
| sat_assert(offset <= (256 - size)); |
| |
| // Cram the data into the right alignment. |
| switch (width) { |
| case 8: |
| sat_assert(&(datacast.l8) == reinterpret_cast<uint8*>(&datacast)); |
| datacast.l8 = value; |
| case 16: |
| sat_assert(&(datacast.l16) == reinterpret_cast<uint16*>(&datacast)); |
| datacast.l16 = value; |
| case 32: |
| datacast.l32 = value; |
| } |
| |
| if (lseek(fd, offset, SEEK_SET) < 0) { |
| logprintf(0, "Process Error: Can't seek %x\n", offset); |
| return; |
| } |
| if (write(fd, &datacast, size) != static_cast<ssize_t>(size)) { |
| logprintf(0, "Process Error: Can't write %x to %x\n", datacast.l32, offset); |
| return; |
| } |
| |
| return; |
| } |
| |
| |
| |
| // Open dev msr. |
| int OsLayer::OpenMSR(uint32 core, uint32 address) { |
| char buf[256]; |
| snprintf(buf, sizeof(buf), "/dev/cpu/%d/msr", core); |
| int fd = open(buf, O_RDWR); |
| if (fd < 0) |
| return fd; |
| |
| uint32 pos = lseek(fd, address, SEEK_SET); |
| if (pos != address) { |
| close(fd); |
| logprintf(5, "Log: can't seek to msr %x, cpu %d\n", address, core); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| bool OsLayer::ReadMSR(uint32 core, uint32 address, uint64 *data) { |
| int fd = OpenMSR(core, address); |
| if (fd < 0) |
| return false; |
| |
| // Read from the msr. |
| bool res = (sizeof(*data) == read(fd, data, sizeof(*data))); |
| |
| if (!res) |
| logprintf(5, "Log: Failed to read msr %x core %d\n", address, core); |
| |
| close(fd); |
| |
| return res; |
| } |
| |
| bool OsLayer::WriteMSR(uint32 core, uint32 address, uint64 *data) { |
| int fd = OpenMSR(core, address); |
| if (fd < 0) |
| return false; |
| |
| // Write to the msr |
| bool res = (sizeof(*data) == write(fd, data, sizeof(*data))); |
| |
| if (!res) |
| logprintf(5, "Log: Failed to write msr %x core %d\n", address, core); |
| |
| close(fd); |
| |
| return res; |
| } |
| |
| // Extract bits [n+len-1, n] from a 32 bit word. |
| // so GetBitField(0x0f00, 8, 4) == 0xf. |
| uint32 OsLayer::GetBitField(uint32 val, uint32 n, uint32 len) { |
| return (val >> n) & ((1<<len) - 1); |
| } |
| |
| // Generic CPU stress workload that would work on any CPU/Platform. |
| // Float-point array moving average calculation. |
| bool OsLayer::CpuStressWorkload() { |
| double float_arr[100]; |
| double sum = 0; |
| #ifdef HAVE_RAND_R |
| unsigned int seed = 12345; |
| #endif |
| |
| // Initialize array with random numbers. |
| for (int i = 0; i < 100; i++) { |
| #ifdef HAVE_RAND_R |
| float_arr[i] = rand_r(&seed); |
| if (rand_r(&seed) % 2) |
| float_arr[i] *= -1.0; |
| #else |
| srand(time(NULL)); |
| float_arr[i] = rand(); // NOLINT |
| if (rand() % 2) // NOLINT |
| float_arr[i] *= -1.0; |
| #endif |
| } |
| |
| // Calculate moving average. |
| for (int i = 0; i < 100000000; i++) { |
| float_arr[i % 100] = |
| (float_arr[i % 100] + float_arr[(i + 1) % 100] + |
| float_arr[(i + 99) % 100]) / 3; |
| sum += float_arr[i % 100]; |
| } |
| |
| // Artificial printf so the loops do not get optimized away. |
| if (sum == 0.0) |
| logprintf(12, "Log: I'm Feeling Lucky!\n"); |
| return true; |
| } |