root/src/apps/powerstatus/ACPIDriverInterface.cpp
/*
 * Copyright 2009-2015, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler, axeld@pinc-software.de
 *              Clemens Zeidler, haiku@clemens-zeidler.de
 */


#include "ACPIDriverInterface.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>

#include <Autolock.h>
#include <Directory.h>
#include <Entry.h>
#include <Path.h>


static const char* kDriverDir = "/dev/power";


RateBuffer::RateBuffer()
        :
        fPosition(0),
        fSize(kRateBufferSize),
        fCurrentSize(0)
{
}


void
RateBuffer::AddRate(int32 rate)
{
        fRateBuffer[fPosition] = rate;
        fPosition ++;
        if (fPosition >= fSize)
                fPosition = 0;

        if (fCurrentSize < fSize)
                fCurrentSize ++;
}


int32
RateBuffer::GetMeanRate()
{
        int32 mean = 0;
        for (int8 i = 0; i < fCurrentSize; i++) {
                mean += fRateBuffer[i];
        }

        if (fCurrentSize == 0)
                return -1;

        return mean / fCurrentSize;
}


// #pragma mark -


Battery::Battery(int driverHandler)
        :
        fDriverHandler(driverHandler)
{
        _Init();
}


Battery::~Battery()
{
        close(fDriverHandler);
}


status_t
Battery::InitCheck()
{
        return fInitStatus;
}


status_t
Battery::UpdateBatteryInfo()
{
        acpi_battery_info info;
        if (ioctl(fDriverHandler, GET_BATTERY_INFO, &info,
                        sizeof(acpi_battery_info)) != 0)
                return errno;

        if ((fExtendedBatteryInfo.last_full_charge > 0
                        && info.capacity > fExtendedBatteryInfo.last_full_charge)
                || info.capacity < 0)
                return B_BAD_DATA;

        if ((fCachedInfo.capacity == 0 && info.capacity > 0)
                || (fCachedInfo.capacity > 0 && info.capacity == 0)) {
                // if capacity went from zero to non-zero or vice-versa, we likely just had a battery
                // connected or disconnected from the system so refresh the full fExtendedBatteryInfo
                GetExtendedBatteryInfo(&fExtendedBatteryInfo);
        }

        fCachedInfo = info;
        return B_OK;
}


status_t
Battery::GetBatteryInfoCached(battery_info* info)
{
        info->state = fCachedInfo.state;
        info->current_rate = fCachedInfo.current_rate;
        info->capacity = fCachedInfo.capacity;
        info->full_capacity = fExtendedBatteryInfo.last_full_charge;
        if (info->full_capacity < 0)
                info->full_capacity = fExtendedBatteryInfo.design_capacity;

        fRateBuffer.AddRate(fCachedInfo.current_rate);
        if (fCachedInfo.current_rate > 0 && fRateBuffer.GetMeanRate() != 0) {
                info->time_left = 3600 * fCachedInfo.capacity
                        / fRateBuffer.GetMeanRate();
        } else
                info->time_left = -1;

        return B_OK;
}


status_t
Battery::GetExtendedBatteryInfo(acpi_extended_battery_info* info)
{
        if (ioctl(fDriverHandler, GET_EXTENDED_BATTERY_INFO, info,
                        sizeof(acpi_extended_battery_info)) != 0)
                return errno;

        return B_OK;
}


void
Battery::_Init()
{
        uint32 magicId = 0;
        if (ioctl(fDriverHandler, IDENTIFY_DEVICE, &magicId, sizeof(uint32)) != 0) {
                fInitStatus = errno;
                return;
        }
        fInitStatus = GetExtendedBatteryInfo(&fExtendedBatteryInfo);
        if (fInitStatus != B_OK)
                return;

        printf("ACPI driver found\n");
        UpdateBatteryInfo();
}


// #pragma mark - ACPIDriverInterface


ACPIDriverInterface::ACPIDriverInterface()
        :
        fInterfaceLocker("acpi interface")
{
}


ACPIDriverInterface::~ACPIDriverInterface()
{
        for (int i = 0; i < fDriverList.CountItems(); i++)
                delete fDriverList.ItemAt(i);
}


status_t
ACPIDriverInterface::Connect()
{
        return _FindDrivers(kDriverDir);
}


status_t
ACPIDriverInterface::GetBatteryInfo(int32 index, battery_info* info)
{
        BAutolock autolock(fInterfaceLocker);
        if (index < 0 || index >= fDriverList.CountItems())
                return B_ERROR;

        return fDriverList.ItemAt(index)->GetBatteryInfoCached(info);
}


status_t
ACPIDriverInterface::GetExtendedBatteryInfo(int32 index,
        acpi_extended_battery_info* info)
{
        BAutolock autolock(fInterfaceLocker);
        if (index < 0 || index >= fDriverList.CountItems())
                return B_ERROR;

        return fDriverList.ItemAt(index)->GetExtendedBatteryInfo(info);
}


int32
ACPIDriverInterface::GetBatteryCount()
{
        return fDriverList.CountItems();
}


status_t
ACPIDriverInterface::_UpdateBatteryInfo()
{
        for (int i = 0; i < fDriverList.CountItems(); i++)
                fDriverList.ItemAt(i)->UpdateBatteryInfo();

        return B_OK;
}


void
ACPIDriverInterface::_WatchPowerStatus()
{
        const bigtime_t kUpdateInterval = 2000000;
                // every two seconds

        while (atomic_get(&fIsWatching) > 0) {
                _UpdateBatteryInfo();
                Broadcast(kMsgUpdate);
                acquire_sem_etc(fWaitSem, 1, B_RELATIVE_TIMEOUT, kUpdateInterval);
        }
}


status_t
ACPIDriverInterface::_FindDrivers(const char* dirpath)
{
        BDirectory dir(dirpath);
        BEntry entry;

        status_t status = B_ERROR;

        while (dir.GetNextEntry(&entry) == B_OK) {
                BPath path;
                entry.GetPath(&path);

                if (entry.IsDirectory()) {
                        if (_FindDrivers(path.Path()) == B_OK)
                                return B_OK;
                } else {
                        int32 handler = open(path.Path(), O_RDWR);
                        if (handler >= 0) {
                                printf("try %s\n", path.Path());
                                Battery* battery = new Battery(handler);
                                if (battery->InitCheck() == B_OK
                                        && fDriverList.AddItem(battery)) {
                                        status = B_OK;
                                } else
                                        delete battery;
                        }
                }
        }
        return status;
}