diff --git a/android/android.make b/android/android.make index e4fd3693de..2f422be736 100644 --- a/android/android.make +++ b/android/android.make @@ -7,13 +7,19 @@ # $Id$ # +# this is a glibc compatibility hack to provide a get_nprocs() replacement. +# The NDK ships cpu-features.c which has a compatible function android_getCpuCount() +CPUFEAT = /home/kugel/share/android-ndk-r6/sources/android/cpufeatures +INCLUDES += -I$(CPUFEAT) +OTHER_SRC += $(CPUFEAT)/cpu-features.c +$(BUILDDIR)/cpu-features.o: $(CPUFEAT)/cpu-features.c + $(call PRINTS,CC $(subst $(ANDROID_NDK_PATH)/,,$<))$(CC) -o $@ -c $^ \ + $(GCCOPTS) -Wno-unused + .SECONDEXPANSION: # $$(JAVA_OBJ) is not populated until after this .SECONDEXPANSION: # $$(OBJ) is not populated until after this .PHONY: apk classes clean dex dirs libs jar -$(BUILDDIR)/$(BINARY): $$(OBJ) $(VOICESPEEXLIB) $(FIRMLIB) $(SKINLIB) - $(call PRINTS,LD $(BINARY))$(CC) -o $@ $^ $(LDOPTS) $(GLOBAL_LDOPTS) - PACKAGE=org.rockbox PACKAGE_PATH=org/rockbox ANDROID_DIR=$(ROOTDIR)/android @@ -103,6 +109,10 @@ dex: $(DEX) classes: $(R_OBJ) $(JAVA_OBJ) + +$(BUILDDIR)/$(BINARY): $$(OBJ) $(VOICESPEEXLIB) $(FIRMLIB) $(SKINLIB) $(BUILDDIR)/cpu-features.o + $(call PRINTS,LD $(BINARY))$(CC) -o $@ $^ $(LDOPTS) $(GLOBAL_LDOPTS) + $(BINLIB_DIR)/$(BINARY): $(BUILDDIR)/$(BINARY) $(call PRINTS,CP $(BINARY))cp $^ $@ diff --git a/apps/debug_menu.c b/apps/debug_menu.c index e0343572b7..5773374d36 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -232,6 +232,108 @@ static bool dbg_os(void) return simplelist_show_list(&info); } +#ifdef __linux__ +#include "cpuinfo-linux.h" + +#define MAX_STATES 16 +static struct time_state states[MAX_STATES]; + +static const char* get_cpuinfo(int selected_item, void *data, + char *buffer, size_t buffer_len) +{ + (void)data;(void)buffer_len; + const char* text; + long time, diff; + struct cpuusage us; + static struct cpuusage last_us; + int state_count = *(int*)data; + + if (cpuusage_linux(&us) != 0) + return NULL; + + switch(selected_item) + { + case 0: + diff = abs(last_us.usage - us.usage); + sprintf(buffer, "Usage: %ld.%02ld%% (%c %ld.%02ld)", + us.usage/100, us.usage%100, + (us.usage >= last_us.usage) ? '+':'-', + diff/100, diff%100); + last_us.usage = us.usage; + return buffer; + case 1: + text = "User"; + time = us.utime; + diff = us.utime - last_us.utime; + last_us.utime = us.utime; + break; + case 2: + text = "Sys"; + time = us.stime; + diff = us.stime - last_us.stime; + last_us.stime = us.stime; + break; + case 3: + text = "Real"; + time = us.rtime; + diff = us.rtime - last_us.rtime; + last_us.rtime = us.rtime; + break; + case 4: + return "*** Per CPU freq stats ***"; + default: + { + int cpu = (selected_item - 5) / (state_count + 1); + int cpu_line = (selected_item - 5) % (state_count + 1); + int freq1 = cpufrequency_linux(cpu); + int freq2 = scalingfrequency_linux(cpu); + if (cpu_line == 0) + { + sprintf(buffer, " CPU%d: Cur/Scal freq: %d/%d MHz", cpu, + freq1 > 0 ? freq1/1000 : -1, + freq2 > 0 ? freq2/1000 : -1); + } + else + { + cpustatetimes_linux(cpu, states, ARRAYLEN(states)); + snprintf(buffer, buffer_len, " %ld %ld", + states[cpu_line-1].frequency, + states[cpu_line-1].time); + } + return buffer; + } + } + sprintf(buffer, "%s: %ld.%02lds (+ %ld.%02ld)", text, + time / us.hz, time % us.hz, + diff / us.hz, diff % us.hz); + return buffer; +} + +static int cpuinfo_cb(int action, struct gui_synclist *lists) +{ + (void)lists; + if (action == ACTION_NONE) + action = ACTION_REDRAW; + return action; +} + +static bool dbg_cpuinfo(void) +{ + struct simplelist_info info; + int cpu_count = MAX(cpucount_linux(), 1); + int state_count = cpustatetimes_linux(0, states, ARRAYLEN(states)); + printf("%s(): %d %d\n", __func__, cpu_count, state_count); + simplelist_info_init(&info, "CPU info:", 5 + cpu_count*(state_count+1), &state_count); + info.get_name = get_cpuinfo; + info.action_callback = cpuinfo_cb; + info.timeout = HZ; + info.hide_selection = true; + info.scroll_all = true; + return simplelist_show_list(&info); +} + +#endif + #ifdef HAVE_LCD_BITMAP #if CONFIG_CODEC != SWCODEC #ifndef SIMULATOR @@ -2062,6 +2164,9 @@ static const struct the_menu_item menuitems[] = { { "Catch mem accesses", dbg_set_memory_guard }, #endif { "View OS stacks", dbg_os }, +#ifdef __linux__ + { "View CPU stats", dbg_cpuinfo }, +#endif #ifdef HAVE_LCD_BITMAP #if (CONFIG_PLATFORM & PLATFORM_NATIVE) { "View battery", view_battery }, @@ -2187,4 +2292,3 @@ bool run_debug_screen(char* screen) } return false; } - diff --git a/firmware/SOURCES b/firmware/SOURCES index b4e9c5ca2b..7053358bee 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -7,6 +7,12 @@ general.c load_code.c powermgmt.c #if (CONFIG_PLATFORM & PLATFORM_HOSTED) + +#ifdef __linux__ +#undef linux +target/hosted/cpuinfo-linux.c +#endif + target/hosted/powermgmt.c target/hosted/rtc.c #endif diff --git a/firmware/target/hosted/cpuinfo-linux.c b/firmware/target/hosted/cpuinfo-linux.c new file mode 100644 index 0000000000..373d1c742e --- /dev/null +++ b/firmware/target/hosted/cpuinfo-linux.c @@ -0,0 +1,198 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2011 Thomas Martitz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kernel.h" +#include "thread.h" +#include "cpuinfo-linux.h" +#include "gcc_extensions.h" + +#undef open /* want the *real* open here, not sim_open or the like */ +#if (CONFIG_PLATFORM & PLATFORM_ANDROID) +#include "cpu-features.h" +#define get_nprocs android_getCpuCount +#endif + +#define NUM_SAMPLES 64 +#define NUM_SAMPLES_MASK (NUM_SAMPLES-1) +#define SAMPLE_RATE 4 + +struct cputime_sample { + struct tms sample; + time_t time; +}; + +static struct cputime_sample samples[NUM_SAMPLES]; +static int current_sample; +static int samples_taken; +static clock_t initial_time, hz; + + +static char cputime_thread_stack[DEFAULT_STACK_SIZE + 0x100]; +static char cputime_thread_name[] = "cputime"; +static int cputime_threadid; +static void cputime_thread(void); + +/* times() can return -1 early after boot */ +static clock_t times_wrapper(struct tms *t) +{ + clock_t res = times(t); + if (res == (clock_t)-1) + res = time(NULL) * hz; + + return res; +} + +static inline void thread_func(void) +{ + struct cputime_sample *s = &samples[current_sample++]; + s->time = times_wrapper(&s->sample); + + current_sample &= NUM_SAMPLES_MASK; + if (samples_taken < NUM_SAMPLES) samples_taken++; + + sleep(HZ/SAMPLE_RATE); +} + +static void NORETURN_ATTR cputime_thread(void) +{ + while(1) + thread_func(); +} + +static void __attribute__((constructor)) get_initial_time(void) +{ + struct tms ign; + hz = sysconf(_SC_CLK_TCK); + initial_time = times_wrapper(&ign); /* dont pass NULL */; +} + +int cpuusage_linux(struct cpuusage* u) +{ + if (UNLIKELY(!cputime_threadid)) + { + /* collect some initial data and start the thread */ + thread_func(); + cputime_threadid = create_thread(cputime_thread, cputime_thread_stack, + sizeof(cputime_thread_stack), 0, cputime_thread_name + IF_PRIO(,PRIORITY_BACKGROUND) IF_COP(, CPU)); + } + if (!u) + return -1; + + clock_t total_cputime; + clock_t diff_utime, diff_stime; + time_t diff_rtime; + int latest_sample = ((current_sample == 0) ? NUM_SAMPLES : current_sample) - 1; + int oldest_sample = (samples_taken < NUM_SAMPLES) ? 0 : current_sample; + + diff_utime = samples[latest_sample].sample.tms_utime - samples[oldest_sample].sample.tms_utime; + diff_stime = samples[latest_sample].sample.tms_stime - samples[oldest_sample].sample.tms_stime; + diff_rtime = samples[latest_sample].time - samples[oldest_sample].time; + if (UNLIKELY(!diff_rtime)) + diff_rtime = 1; + u->hz = hz; + + u->utime = samples[latest_sample].sample.tms_utime; + u->stime = samples[latest_sample].sample.tms_stime; + u->rtime = samples[latest_sample].time - initial_time; + + total_cputime = diff_utime + diff_stime; + total_cputime *= 100; /* pump up by 100 for hundredth */ + u->usage = total_cputime * 100 / diff_rtime; + + return 0; +} + +int cpucount_linux(void) +{ + return get_nprocs(); +} + +int cpufrequency_linux(int cpu) +{ + char path[64]; + char temp[10]; + int cpu_dev, ret; + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_cur_freq", cpu); + cpu_dev = open(path, O_RDONLY); + if (cpu_dev < 0) + return -1; + read(cpu_dev, temp, 10); + ret = atoi(temp); + close(cpu_dev); + return ret; +} + +int scalingfrequency_linux(int cpu) +{ + char path[64]; + char temp[10]; + int cpu_dev, ret; + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu); + cpu_dev = open(path, O_RDONLY); + if (cpu_dev < 0) + return -1; + read(cpu_dev, temp, 10); + ret = atoi(temp); + close(cpu_dev); + return ret; +} + +int cpustatetimes_linux(int cpu, struct time_state* data, int max_elements) +{ + int elements_left = max_elements, cpu_dev; + char buf[256], path[64], *p; + ssize_t read_size; + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state", cpu); + cpu_dev = open(path, O_RDONLY); + if (cpu_dev < 0) + return -1; + read_size = read(cpu_dev, buf, sizeof(buf)); + + close(cpu_dev); + + p = buf; + while(elements_left > 0 && (p-buf) < read_size) + { + data->frequency = atol(p); + /* this loop breaks when it seems the space or line-feed, + * so buf points to a number aftwards */ + while(isdigit(*p++)); + data->time = atol(p); + /* now skip over to the next line */ + while(isdigit(*p++)); + data++; + elements_left--; + } + + return (max_elements - elements_left) ?: -1; +} diff --git a/firmware/target/hosted/cpuinfo-linux.h b/firmware/target/hosted/cpuinfo-linux.h new file mode 100644 index 0000000000..d9ba376f49 --- /dev/null +++ b/firmware/target/hosted/cpuinfo-linux.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2011 Thomas Martitz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#ifndef __CPUINFO_LINUX_H__ +#define __CPUINFO_LINUX_H__ + +struct cpuusage { + long usage; /* in hundredth percent */ + long utime; /* in clock ticks */ + long stime; /* in clock ticks */ + long rtime; /* in clock ticks */ + long hz; /* how clock ticks per second */ +}; + +struct time_state { + long frequency; + long time; +}; + +int cpuusage_linux(struct cpuusage* u); +int cpufrequency_linux(int cpu); +int scalingfrequency_linux(int cpu); +int cpustatetimes_linux(int cpu, struct time_state* data, int max_elements); +int cpucount_linux(void); + +#endif /* __CPUINFO_LINUX_H__ */