1 /* Copyright (C) 2010-2020 The RetroArch team
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (features_cpu.c).
5 * ---------------------------------------------------------------------------------------
7 * Permission is hereby granted, free of charge,
8 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation the rights to
10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 #include <compat/strl.h>
33 #include <streams/file_stream.h>
35 #include <features/features_cpu.h>
36 #include <retro_timers.h>
38 #if defined(_WIN32) && !defined(_XBOX)
43 #include <lv2/systime.h>
47 #include <PPCIntrinsics.h>
48 #elif !defined(__MACH__) && (defined(__POWERPC__) || defined(__powerpc__) || defined(__ppc__) || defined(__PPC64__) || defined(__powerpc64__))
49 #ifndef _PPU_INTRINSICS_H
50 #include <ppu_intrinsics.h>
52 #elif defined(_POSIX_MONOTONIC_CLOCK) || defined(ANDROID) || defined(__QNX__) || defined(DJGPP)
53 /* POSIX_MONOTONIC_CLOCK is not being defined in Android headers despite support being present. */
57 #if defined(__QNX__) && !defined(CLOCK_MONOTONIC)
58 #define CLOCK_MONOTONIC 2
62 #include <pspkernel.h>
65 #if defined(PSP) || defined(__PSL1GHT__)
74 #include <psp2/kernel/processmgr.h>
79 #include <orbis/libkernel.h>
83 #include <ps2sdkapi.h>
86 #if !defined(__PSL1GHT__) && defined(__PS3__)
87 #include <sys/sys_time.h>
91 #include <ogc/lwp_watchdog.h>
95 #include <wiiu/os/time.h>
98 #if defined(HAVE_LIBNX)
100 #elif defined(SWITCH)
101 #include <libtransistor/types.h>
102 #include <libtransistor/svc.h>
108 #include <3ds/services/cfgu.h>
111 /* iOS/OSX specific. Lacks clock_gettime(), so implement it. */
113 #include <sys/time.h>
115 #ifndef CLOCK_MONOTONIC
116 #define CLOCK_MONOTONIC 0
119 #ifndef CLOCK_REALTIME
120 #define CLOCK_REALTIME 0
124 * TODO/FIXME: clock_gettime function is part of iOS 10 now
126 static int ra_clock_gettime(int clk_ik, struct timespec *t)
129 int rv = gettimeofday(&now, NULL);
132 t->tv_sec = now.tv_sec;
133 t->tv_nsec = now.tv_usec * 1000;
138 #if defined(__MACH__) && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
140 #define ra_clock_gettime clock_gettime
144 #include <emscripten.h>
147 #if defined(BSD) || defined(__APPLE__)
148 #include <sys/sysctl.h>
154 * cpu_features_get_perf_counter:
156 * Gets performance counter.
158 * @return Performance counter.
160 retro_perf_tick_t cpu_features_get_perf_counter(void)
162 retro_perf_tick_t time_ticks = 0;
164 long tv_sec, tv_usec;
165 #if defined(_MSC_VER) && _MSC_VER <= 1200
166 static const unsigned __int64 epoch = 11644473600000000;
168 static const unsigned __int64 epoch = 11644473600000000ULL;
171 SYSTEMTIME system_time;
172 ULARGE_INTEGER ularge;
174 GetSystemTime(&system_time);
175 SystemTimeToFileTime(&system_time, &file_time);
176 ularge.LowPart = file_time.dwLowDateTime;
177 ularge.HighPart = file_time.dwHighDateTime;
179 tv_sec = (long)((ularge.QuadPart - epoch) / 10000000L);
180 tv_usec = (long)(system_time.wMilliseconds * 1000);
181 time_ticks = (1000000 * tv_sec + tv_usec);
183 time_ticks = gettime();
184 #elif !defined(__MACH__) && (defined(_XBOX360) || defined(__powerpc__) || defined(__ppc__) || defined(__POWERPC__) || defined(__PSL1GHT__) || defined(__PPC64__) || defined(__powerpc64__))
185 time_ticks = __mftb();
186 #elif (defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0) || defined(__QNX__) || defined(ANDROID)
188 if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0)
189 time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 +
190 (retro_perf_tick_t)tv.tv_nsec;
192 #elif defined(__GNUC__) && defined(__i386__) || defined(__i486__) || defined(__i686__) || defined(_M_X64) || defined(_M_AMD64)
193 __asm__ volatile ("rdtsc" : "=A" (time_ticks));
194 #elif defined(__GNUC__) && defined(__x86_64__) || defined(_M_IX86)
196 __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
197 time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32);
198 #elif defined(__ARM_ARCH_6__)
199 __asm__ volatile( "mrc p15, 0, %0, c9, c13, 0" : "=r"(time_ticks) );
200 #elif defined(__aarch64__)
201 __asm__ volatile( "mrs %0, cntvct_el0" : "=r"(time_ticks) );
202 #elif defined(PSP) || defined(VITA)
203 time_ticks = sceKernelGetSystemTimeWide();
205 sceRtcGetCurrentTick((SceRtcTick*)&time_ticks);
207 time_ticks = ps2_clock();
209 time_ticks = svcGetSystemTick();
211 time_ticks = OSGetSystemTime();
212 #elif defined(HAVE_LIBNX)
213 time_ticks = armGetSystemTick();
214 #elif defined(EMSCRIPTEN)
215 time_ticks = emscripten_get_now() * 1000;
222 * cpu_features_get_time_usec:
224 * Gets time in microseconds.
226 * @return Time in microseconds.
228 retro_time_t cpu_features_get_time_usec(void)
231 static LARGE_INTEGER freq;
234 /* Frequency is guaranteed to not change. */
235 if (!freq.QuadPart && !QueryPerformanceFrequency(&freq))
238 if (!QueryPerformanceCounter(&count))
240 return (count.QuadPart / freq.QuadPart * 1000000) + (count.QuadPart % freq.QuadPart * 1000000 / freq.QuadPart);
241 #elif defined(__PSL1GHT__)
242 return sysGetSystemTime();
243 #elif !defined(__PSL1GHT__) && defined(__PS3__)
244 return sys_time_get_system_time();
246 return ticks_to_microsecs(gettime());
248 return ticks_to_us(OSGetSystemTime());
249 #elif defined(SWITCH) || defined(HAVE_LIBNX)
250 return (svcGetSystemTick() * 10) / 192;
252 return osGetTime() * 1000;
253 #elif defined(_POSIX_MONOTONIC_CLOCK) || defined(__QNX__) || defined(ANDROID) || defined(__MACH__)
255 if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) < 0)
257 return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000;
258 #elif defined(EMSCRIPTEN)
259 return emscripten_get_now() * 1000;
261 return ps2_clock() / PS2_CLOCKS_PER_MSEC * 1000;
262 #elif defined(VITA) || defined(PSP)
263 return sceKernelGetSystemTimeWide();
265 return uclock() * 1000000LL / UCLOCKS_PER_SEC;
267 return sceKernelGetProcessTime();
269 #error "Your platform does not have a timer function implemented in cpu_features_get_time_usec(). Cannot continue."
273 #if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__) || (defined(_M_X64) && _MSC_VER > 1310) || (defined(_M_IX86) && _MSC_VER > 1310)
277 #if defined(_MSC_VER) && !defined(_XBOX)
278 #if (_MSC_VER > 1310)
283 #if defined(CPU_X86) && !defined(__MACH__)
284 void x86_cpuid(int func, int flags[4])
286 /* On Android, we compile RetroArch with PIC, and we
287 * are not allowed to clobber the ebx register. */
296 #if defined(__GNUC__)
298 "mov %%" REG_b ", %%" REG_S "\n"
300 "xchg %%" REG_b ", %%" REG_S "\n"
301 : "=a"(flags[0]), "=S"(flags[1]), "=c"(flags[2]), "=d"(flags[3])
303 #elif defined(_MSC_VER)
304 __cpuid(flags, func);
307 printf("Unknown compiler. Cannot check CPUID with inline assembly.\n");
309 memset(flags, 0, 4 * sizeof(int));
313 /* Only runs on i686 and above. Needs to be conditionally run. */
314 static uint64_t xgetbv_x86(uint32_t idx)
316 #if defined(__GNUC__)
319 /* Older GCC versions (Apple's GCC for example) do
320 * not understand xgetbv instruction.
321 * Stamp out the machine code directly.
323 ".byte 0x0f, 0x01, 0xd0\n"
324 : "=a"(eax), "=d"(edx) : "c"(idx));
325 return ((uint64_t)edx << 32) | eax;
326 #elif _MSC_FULL_VER >= 160040219
327 /* Intrinsic only works on 2010 SP1 and above. */
331 printf("Unknown compiler. Cannot check xgetbv bits.\n");
338 #if defined(__ARM_NEON__)
340 static void arm_enable_runfast_mode(void)
342 /* RunFast mode. Enables flush-to-zero and some
343 * floating point optimizations. */
344 static const unsigned x = 0x04086060;
345 static const unsigned y = 0x03000000;
348 "fmrx %0, fpscr \n\t" /* r0 = FPSCR */
349 "and %0, %0, %1 \n\t" /* r0 = r0 & 0x04086060 */
350 "orr %0, %0, %2 \n\t" /* r0 = r0 | 0x03000000 */
351 "fmxr fpscr, %0 \n\t" /* FPSCR = r0 */
359 #if defined(__linux__) && !defined(CPU_X86)
360 static unsigned char check_arm_cpu_feature(const char* feature)
363 unsigned char status = 0;
364 RFILE *fp = filestream_open("/proc/cpuinfo",
365 RETRO_VFS_FILE_ACCESS_READ,
366 RETRO_VFS_FILE_ACCESS_HINT_NONE);
371 while (filestream_gets(fp, line, sizeof(line)))
373 if (strncmp(line, "Features\t: ", 11))
376 if (strstr(line + 11, feature))
382 filestream_close(fp);
387 #if !defined(_SC_NPROCESSORS_ONLN)
391 * Parse an decimal integer starting from 'input', but not going further
392 * than 'limit'. Return the value into '*result'.
394 * NOTE: Does not skip over leading spaces, or deal with sign characters.
395 * NOTE: Ignores overflows.
397 * The function returns NULL in case of error (bad format), or the new
398 * position after the decimal number in case of success (which will always
403 static const char *parse_decimal(const char* input,
404 const char* limit, int* result)
406 const char* p = input;
412 if ((unsigned)d >= 10U)
426 * Parse a textual list of cpus and store the result inside a CpuList object.
427 * Input format is the following:
428 * - comma-separated list of items (no spaces)
429 * - each item is either a single decimal number (cpu index), or a range made
430 * of two numbers separated by a single dash (-). Ranges are inclusive.
436 static void cpulist_parse(CpuList* list, char **buf, ssize_t length)
438 const char* p = (const char*)buf;
439 const char* end = p + length;
441 /* NOTE: the input line coming from sysfs typically contains a
442 * trailing newline, so take care of it in the code below
444 while (p < end && *p != '\n')
446 int val, start_value, end_value;
447 /* Find the end of current item, and put it into 'q' */
448 const char *q = (const char*)memchr(p, ',', end-p);
453 /* Get first value */
454 if (!(p = parse_decimal(p, q, &start_value)))
457 end_value = start_value;
459 /* If we're not at the end of the item, expect a dash and
460 * and integer; extract end value.
462 if (p < q && *p == '-')
464 if (!(p = parse_decimal(p+1, q, &end_value)))
468 /* Set bits CPU list bits */
469 for (val = start_value; val <= end_value; val++)
471 if ((unsigned)val < 32)
472 list->mask |= (uint32_t)(UINT32_C(1) << val);
475 /* Jump to next item */
485 * Read a CPU list from one sysfs file
487 static void cpulist_read_from(CpuList* list, const char* filename)
494 if (filestream_read_file(filename, (void**)&buf, &length) != 1)
497 cpulist_parse(list, &buf, length);
507 * cpu_features_get_core_amount:
509 * Gets the amount of available CPU cores.
511 * @return Amount of CPU cores available.
513 unsigned cpu_features_get_core_amount(void)
515 #if defined(_WIN32) && !defined(_XBOX)
518 #if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
519 GetNativeSystemInfo(&sysinfo);
521 GetSystemInfo(&sysinfo);
523 return sysinfo.dwNumberOfProcessors;
526 #elif defined(PSP) || defined(PS2)
528 #elif defined(__PSL1GHT__) || !defined(__PSL1GHT__) && defined(__PS3__)
529 return 1; /* Only one PPU, SPUs don't really count */
532 #elif defined(HAVE_LIBNX) || defined(SWITCH)
535 u8 device_model = 0xFF;
536 CFGU_GetSystemModel(&device_model);/*(0 = O3DS, 1 = O3DSXL, 2 = N3DS, 3 = 2DS, 4 = N3DSXL, 5 = N2DSXL)*/
537 switch (device_model)
552 /*Unknown Device Or Check Failed*/
558 #elif defined(_SC_NPROCESSORS_ONLN)
559 /* Linux, most UNIX-likes. */
560 long ret = sysconf(_SC_NPROCESSORS_ONLN);
563 return (unsigned)ret;
564 #elif defined(BSD) || defined(__APPLE__)
566 /* Copypasta from stackoverflow, dunno if it works. */
569 size_t len = sizeof(num_cpu);
572 mib[1] = HW_AVAILCPU;
573 sysctl(mib, 2, &num_cpu, &len, NULL, 0);
577 sysctl(mib, 2, &num_cpu, &len, NULL, 0);
582 #elif defined(__linux__)
583 CpuList cpus_present[1];
584 CpuList cpus_possible[1];
587 cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present");
588 cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible");
590 /* Compute the intersection of both sets to get the actual number of
591 * CPU cores that can be used on this device by the kernel.
593 cpus_present->mask &= cpus_possible->mask;
594 amount = __builtin_popcount(cpus_present->mask);
599 #elif defined(_XBOX360)
602 /* No idea, assume single core. */
607 /* According to http://en.wikipedia.org/wiki/CPUID */
608 #define VENDOR_INTEL_b 0x756e6547
609 #define VENDOR_INTEL_c 0x6c65746e
610 #define VENDOR_INTEL_d 0x49656e69
615 * Gets CPU features..
617 * @return Bitmask of all CPU features available.
619 uint64_t cpu_features_get(void)
622 #if defined(CPU_X86) && !defined(__MACH__)
623 int vendor_is_intel = 0;
624 const int avx_flags = (1 << 27) | (1 << 28);
626 #if defined(__MACH__)
627 size_t len = sizeof(size_t);
629 if (sysctlbyname("hw.optional.floatingpoint", NULL, &len, NULL, 0) == 0)
630 cpu |= RETRO_SIMD_CMOV;
633 len = sizeof(size_t);
634 if (sysctlbyname("hw.optional.mmx", NULL, &len, NULL, 0) == 0)
635 cpu |= RETRO_SIMD_MMX | RETRO_SIMD_MMXEXT;
637 len = sizeof(size_t);
638 if (sysctlbyname("hw.optional.sse", NULL, &len, NULL, 0) == 0)
639 cpu |= RETRO_SIMD_SSE;
641 len = sizeof(size_t);
642 if (sysctlbyname("hw.optional.sse2", NULL, &len, NULL, 0) == 0)
643 cpu |= RETRO_SIMD_SSE2;
645 len = sizeof(size_t);
646 if (sysctlbyname("hw.optional.sse3", NULL, &len, NULL, 0) == 0)
647 cpu |= RETRO_SIMD_SSE3;
649 len = sizeof(size_t);
650 if (sysctlbyname("hw.optional.supplementalsse3", NULL, &len, NULL, 0) == 0)
651 cpu |= RETRO_SIMD_SSSE3;
653 len = sizeof(size_t);
654 if (sysctlbyname("hw.optional.sse4_1", NULL, &len, NULL, 0) == 0)
655 cpu |= RETRO_SIMD_SSE4;
657 len = sizeof(size_t);
658 if (sysctlbyname("hw.optional.sse4_2", NULL, &len, NULL, 0) == 0)
659 cpu |= RETRO_SIMD_SSE42;
661 len = sizeof(size_t);
662 if (sysctlbyname("hw.optional.aes", NULL, &len, NULL, 0) == 0)
663 cpu |= RETRO_SIMD_AES;
665 len = sizeof(size_t);
666 if (sysctlbyname("hw.optional.avx1_0", NULL, &len, NULL, 0) == 0)
667 cpu |= RETRO_SIMD_AVX;
669 len = sizeof(size_t);
670 if (sysctlbyname("hw.optional.avx2_0", NULL, &len, NULL, 0) == 0)
671 cpu |= RETRO_SIMD_AVX2;
673 len = sizeof(size_t);
674 if (sysctlbyname("hw.optional.altivec", NULL, &len, NULL, 0) == 0)
675 cpu |= RETRO_SIMD_VMX;
678 len = sizeof(size_t);
679 if (sysctlbyname("hw.optional.neon", NULL, &len, NULL, 0) == 0)
680 cpu |= RETRO_SIMD_NEON;
682 len = sizeof(size_t);
683 if (sysctlbyname("hw.optional.neon_fp16", NULL, &len, NULL, 0) == 0)
684 cpu |= RETRO_SIMD_VFPV3;
686 len = sizeof(size_t);
687 if (sysctlbyname("hw.optional.neon_hpfp", NULL, &len, NULL, 0) == 0)
688 cpu |= RETRO_SIMD_VFPV4;
690 #elif defined(_XBOX1)
691 cpu |= RETRO_SIMD_MMX | RETRO_SIMD_SSE | RETRO_SIMD_MMXEXT;
692 #elif defined(CPU_X86)
693 unsigned max_flag = 0;
695 int vendor_shuffle[3];
698 vendor_shuffle[0] = flags[1];
699 vendor_shuffle[1] = flags[3];
700 vendor_shuffle[2] = flags[2];
703 memcpy(vendor, vendor_shuffle, sizeof(vendor_shuffle));
705 /* printf("[CPUID]: Vendor: %s\n", vendor); */
708 flags[1] == VENDOR_INTEL_b &&
709 flags[2] == VENDOR_INTEL_c &&
710 flags[3] == VENDOR_INTEL_d);
713 if (max_flag < 1) /* Does CPUID not support func = 1? (unlikely ...) */
718 if (flags[3] & (1 << 15))
719 cpu |= RETRO_SIMD_CMOV;
721 if (flags[3] & (1 << 23))
722 cpu |= RETRO_SIMD_MMX;
724 /* SSE also implies MMXEXT (according to FFmpeg source). */
725 if (flags[3] & (1 << 25))
726 cpu |= RETRO_SIMD_SSE | RETRO_SIMD_MMXEXT;
728 if (flags[3] & (1 << 26))
729 cpu |= RETRO_SIMD_SSE2;
731 if (flags[2] & (1 << 0))
732 cpu |= RETRO_SIMD_SSE3;
734 if (flags[2] & (1 << 9))
735 cpu |= RETRO_SIMD_SSSE3;
737 if (flags[2] & (1 << 19))
738 cpu |= RETRO_SIMD_SSE4;
740 if (flags[2] & (1 << 20))
741 cpu |= RETRO_SIMD_SSE42;
743 if ((flags[2] & (1 << 23)))
744 cpu |= RETRO_SIMD_POPCNT;
746 if (vendor_is_intel && (flags[2] & (1 << 22)))
747 cpu |= RETRO_SIMD_MOVBE;
749 if (flags[2] & (1 << 25))
750 cpu |= RETRO_SIMD_AES;
752 /* Must only perform xgetbv check if we have
753 * AVX CPU support (guaranteed to have at least i686). */
754 if (((flags[2] & avx_flags) == avx_flags)
755 && ((xgetbv_x86(0) & 0x6) == 0x6))
756 cpu |= RETRO_SIMD_AVX;
761 if (flags[1] & (1 << 5))
762 cpu |= RETRO_SIMD_AVX2;
765 x86_cpuid(0x80000000, flags);
767 if (max_flag >= 0x80000001u)
769 x86_cpuid(0x80000001, flags);
770 if (flags[3] & (1 << 23))
771 cpu |= RETRO_SIMD_MMX;
772 if (flags[3] & (1 << 22))
773 cpu |= RETRO_SIMD_MMXEXT;
775 #elif defined(__linux__)
776 if (check_arm_cpu_feature("neon"))
778 cpu |= RETRO_SIMD_NEON;
779 #if defined(__ARM_NEON__) && defined(__arm__)
780 arm_enable_runfast_mode();
784 if (check_arm_cpu_feature("vfpv3"))
785 cpu |= RETRO_SIMD_VFPV3;
787 if (check_arm_cpu_feature("vfpv4"))
788 cpu |= RETRO_SIMD_VFPV4;
790 if (check_arm_cpu_feature("asimd"))
792 cpu |= RETRO_SIMD_ASIMD;
794 cpu |= RETRO_SIMD_NEON;
796 arm_enable_runfast_mode();
802 check_arm_cpu_feature("swp");
803 check_arm_cpu_feature("half");
804 check_arm_cpu_feature("thumb");
805 check_arm_cpu_feature("fastmult");
806 check_arm_cpu_feature("vfp");
807 check_arm_cpu_feature("edsp");
808 check_arm_cpu_feature("thumbee");
809 check_arm_cpu_feature("tls");
810 check_arm_cpu_feature("idiva");
811 check_arm_cpu_feature("idivt");
814 #elif defined(__ARM_NEON__)
815 cpu |= RETRO_SIMD_NEON;
817 arm_enable_runfast_mode();
819 #elif defined(__ALTIVEC__)
820 cpu |= RETRO_SIMD_VMX;
821 #elif defined(XBOX360)
822 cpu |= RETRO_SIMD_VMX128;
823 #elif defined(PSP) || defined(PS2)
824 cpu |= RETRO_SIMD_VFPU;
826 cpu |= RETRO_SIMD_PS;
832 void cpu_features_get_model_name(char *name, int len)
834 #if defined(CPU_X86) && !defined(__MACH__)
847 x86_cpuid(0x80000000, flags.i);
849 /* Check for additional cpuid attributes availability */
850 if (flags.u[0] < 0x80000004)
853 for (i = 0; i < 3; i++)
855 memset(flags.i, 0, sizeof(flags.i));
856 x86_cpuid(0x80000002 + i, flags.i);
858 for (j = 0; j < (int)sizeof(flags.s); j++)
860 if (!start && flags.s[j] == ' ')
867 /* truncate if we ran out of room */
872 name[pos++] = flags.s[j];
876 /* terminate our string */
879 #elif defined(__MACH__)
883 size_t len_size = len;
884 sysctlbyname("machdep.cpu.brand_string", name, &len_size, NULL, 0);
886 #elif defined(__linux__)
890 char *model_name, line[128];
891 RFILE *fp = filestream_open("/proc/cpuinfo",
892 RETRO_VFS_FILE_ACCESS_READ,
893 RETRO_VFS_FILE_ACCESS_HINT_NONE);
898 while (filestream_gets(fp, line, sizeof(line)))
900 if (strncmp(line, "model name", 10))
903 if ((model_name = strstr(line + 10, ": ")))
906 strncpy(name, model_name, len);
907 name[len - 1] = '\0';
913 filestream_close(fp);