 /**************************************************************************
 *   Copyright (C) 2006 by Daniel Gollub                                   *
 *                            <dgollub@suse.de>                            *
 *                         Danny Kukawka                                   *
 *                            <dkukawka@suse.de>, <danny.kukawka@web.de>   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License     *
 *   as published by the Free Software Foundation.                         *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/

/*! 
 * \file 	hardware_cpu.cpp
 * \brief 	In this file can be found the CPU information related code. 
 * \author 	Danny Kukawka, <dkukawka@suse.de>, <danny.kukawka@web.de>
 *  \author 	Daniel Gollub, <dgollub@suse.de>
 * \author 	
 * \date    	2006
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// include own header
#include "hardware_cpu.h"
#include "hardware_cpu.moc"

// QT Header
#include <tqdir.h>
#include <tqtimer.h>

// system header
#include <fcntl.h>
#include <unistd.h>

/*! The default constructor of the class CPUInfo */
CPUInfo::CPUInfo() {
	kdDebugFuncIn(trace);

	update_info_cpufreq_speed_changed = true;
	numOfCPUs = -1;

	m_hwdevices = TDEGlobal::hardwareDevices();
	m_hwdevices->setBatteryUpdatesEnabled(true);

	kdDebugFuncOut(trace);
}

/*! The default desctuctor of the class CPUInfo */
CPUInfo::~CPUInfo() {
	kdDebugFuncIn(trace);
}

/*!
 * This function counts all online/offline CPUS.
 * Returns the total count of CPUs - _not_ the last CPU ID!
 */
int CPUInfo::getCPUNum() {
	TDEGenericHardwareList hwlist = m_hwdevices->listByDeviceClass(TDEGenericDeviceType::CPU);
	return hwlist.count();
}

/*!
 * The function checks the current CPU Speed. The current cpu speed needs to be read out from 
 * sysfs and currently not be obtained through the daemon. If the CPUFreg changed the new value
 * is set to \ref cpufreq_speed .
 * \return Information if something changed or if there are errors as an interger value
 * \retval 	-1 if there are error by reading from /sys/..
 * \retval	 0 if nothing changed
 * \retval 	 1 if something changed
 */
int CPUInfo::checkCPUSpeed(){
	kdDebugFuncOut(trace);

	TDEGenericHardwareList hwlist = m_hwdevices->listByDeviceClass(TDEGenericDeviceType::CPU);

	bool speed_changed = false;
	int new_value = -1;

	// first check path for the kernel on-demand-govenour then 
	// for the use userspace case
	update_info_cpufreq_speed_changed = false;
	cpufreq_speed.clear();

	if (numOfCPUs == -1) {
		numOfCPUs = hwlist.count();
	}

	for (int cpu_id=0; cpu_id < numOfCPUs; cpu_id++) {
		TDECPUDevice* cdevice = NULL;
		TDEGenericDevice *hwdevice;
		for (hwdevice = hwlist.first(); hwdevice; hwdevice = hwlist.next()) {
			TDECPUDevice* possiblecpu = static_cast<TDECPUDevice*>(hwdevice);
			if (possiblecpu) {
				if (possiblecpu->coreNumber() == cpu_id) {
					cdevice = possiblecpu;
				}
			}
		}

		new_value = -1;

		if (cdevice) {
			new_value = cdevice->frequency();
		}
		else {
			speed_changed = true;
			// CPU disabled -> set Freq to -1
			cpufreq_speed.append(-1);
		}

		if (new_value != cpufreq_speed[cpu_id]) {
			speed_changed = true;
			cpufreq_speed.append(new_value);
		}
	}
	
	if (speed_changed) {
		update_info_cpufreq_speed_changed = true;
		kdDebugFuncOut(trace);
		return 1;
	}

	kdDebugFuncOut(trace);
	return 0;
}

/*!
 * The function checks the Speed of throttling CPU(s). The cpu speed needs to be read out from 
 * /proc/cpuinfo.
 * \return Success or error while reading /proc/cpuinfo 
 * \retval	  0 successful
 * \retval 	 -1 reading problem
 */
int CPUInfo::checkCPUSpeedThrottling() {
	kdDebugFuncOut(trace);

	TQString cpu_file = "/proc/cpuinfo";
#ifdef FAKE_CPU
	cpu_file.prepend("/tmp/foo");
#endif	
	TQFile cpu_info(cpu_file);

	// clear cpufreq list
	cpufreq_speed.clear();

	if ( !cpu_info.open(IO_ReadOnly) ) {
		cpu_info.close();
		kdDebugFuncOut(trace);
		return -1;
	}

	TQTextStream stream( &cpu_info );
	TQString line;

	while ( !stream.atEnd() ) {
		line = stream.readLine();
		
		if (line.startsWith("cpu MHz		: ")) {
			line.remove("cpu MHz		: ");
			line = line.remove(line.length() - 4, 4);
			cpufreq_speed.append(line.toInt());
		}
	}
	
	while ((int) cpufreq_speed.count() < numOfCPUs) {
		cpufreq_speed.append(-1);
	}

	cpu_info.close();
	kdDebugFuncOut(trace);
	return 0;
}

/*!
 * The function gets the current throttling state of the CPU(s). The throttling state needs to be 
 * read out from /proc/acpi/processor/CPUX/throttling.
 * \return boolean with info if throttling is supported
 * \retval true if throttling is supported
 * \retval false if not supported or on any other error
 */
bool CPUInfo::getCPUThrottlingState() {
	kdDebugFuncIn(trace);

	int id = 0;
	TQFileInfo *fi;
	TQString cpu_dirname;
	TQString dir_acpi_processor = "/proc/acpi/processor/";
#ifdef FAKE_CPU
	dir_acpi_processor.prepend("/tmp/foo");
#endif

	TQDir d_throttling(dir_acpi_processor);
	if (!d_throttling.exists()) {
		kdDebugFuncOut(trace);
		return false;
	}

	d_throttling.setFilter( TQDir::Dirs );
	d_throttling.setNameFilter("CPU*");

	const TQFileInfoList *list = d_throttling.entryInfoList();
	TQFileInfoListIterator it( *list );

	// clear throttling value list
	cpu_throttling.clear();

	while ((fi = it.current()) != 0 ) {
		cpu_dirname = fi->fileName();

		TQString throttling_device = d_throttling.absPath();
		throttling_device.append("/").append(cpu_dirname).append("/throttling");

		kdDebug() << "Throttling state file for CPU" << id << " will be: " << throttling_device << endl;

		TQFile throttling(throttling_device);

		// open throttling state file
		if ( throttling.open(IO_ReadOnly) ) {
			TQTextStream stream( &throttling );
			TQString line;

			do {
				line = stream.readLine();
			} while (!line.startsWith("   *T") && !stream.atEnd() );

			if (line.startsWith("   *T")) {
				line = line.right(3);
				line.remove("%");
				cpu_throttling.append(line.toInt());
				kdDebug () << "CPU " << id 
					   << ": cpu_throttling is set to: " << cpu_throttling[id] << endl;
			} else {
				cpu_throttling.append(0);
			}
		}

		throttling.close();
		++it; // next entry
		id++; // count cpu id
	}

	kdDebugFuncOut(trace);
	return true;
}

/*!
 * The function gets the Max CPU Speed. The max cpu speed needs to be read out from 
 * sysfs and currently not be obtained through the daemon. 
 */
void CPUInfo::getCPUMaxSpeed() {
	kdDebugFuncIn(trace);

	TDEGenericHardwareList hwlist = m_hwdevices->listByDeviceClass(TDEGenericDeviceType::CPU);

	cpufreq_max_speed.clear();

	if (numOfCPUs == -1) {
		numOfCPUs = getCPUNum();
	}

	for (int cpu_id=0; cpu_id < numOfCPUs; cpu_id++) {
		TDECPUDevice* cdevice = NULL;
		TDEGenericDevice *hwdevice;
		for (hwdevice = hwlist.first(); hwdevice; hwdevice = hwlist.next()) {
			TDECPUDevice* possiblecpu = static_cast<TDECPUDevice*>(hwdevice);
			if (possiblecpu) {
				if (possiblecpu->coreNumber() == cpu_id) {
					cdevice = possiblecpu;
				}
			}
		}

		if (cdevice) {
			cpufreq_max_speed.append(cdevice->maxFrequency());
		}
		else {
			cpufreq_max_speed.append(-1);
		}
	}

	kdDebugFuncOut(trace);
}
