root/src/system/kernel/system_info.cpp
/*
 * Copyright (c) 2004-2020, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Stefano Ceccherini
 *              Axel Dörfler, axeld@pinc-software.de
 *              Paweł Dziepak, pdziepak@quarnos.org
 *              Ingo Weinhold, ingo_weinhold@gmx.de
 */


#include <ksystem_info.h>
#include <system_info.h>
#include <system_revision.h>
#include <arch/system_info.h>

#include <string.h>

#include <algorithm>

#include <OS.h>
#include <KernelExport.h>

#include <AutoDeleter.h>

#include <block_cache.h>
#include <cpu.h>
#include <debug.h>
#include <kernel.h>
#include <lock.h>
#include <Notifications.h>
#include <messaging.h>
#include <port.h>
#include <real_time_clock.h>
#include <sem.h>
#include <smp.h>
#include <team.h>
#include <thread.h>
#include <util/AutoLock.h>
#include <vm/vm.h>
#include <vm/vm_page.h>


const static int64 kKernelVersion = B_HAIKU_VERSION;
const static char *kKernelName = "kernel_" HAIKU_ARCH;


static int
dump_info(int argc, char **argv)
{
        kprintf("kernel build: %s %s (gcc%d %s), debug level %d\n", __DATE__,
                __TIME__, __GNUC__, __VERSION__, KDEBUG_LEVEL);
        kprintf("revision: %s\n\n", get_haiku_revision());

        kprintf("cpu count: %" B_PRId32 "\n", smp_get_num_cpus());

        for (int32 i = 0; i < smp_get_num_cpus(); i++)
                kprintf("  [%" B_PRId32 "] active time: %10" B_PRId64 ", interrupt"
                        " time: %10" B_PRId64 ", irq time: %10" B_PRId64 "\n", i + 1,
                        gCPU[i].active_time, gCPU[i].interrupt_time, gCPU[i].irq_time);

        // ToDo: Add page_faults
        kprintf("pages:\t\t%" B_PRIuPHYSADDR " (%" B_PRIuPHYSADDR " max)\n",
                vm_page_num_pages() - vm_page_num_free_pages(), vm_page_num_pages());

        kprintf("sems:\t\t%" B_PRId32 " (%" B_PRId32 " max)\n", sem_used_sems(),
                sem_max_sems());
        kprintf("ports:\t\t%" B_PRId32 " (%" B_PRId32 " max)\n", port_used_ports(),
                        port_max_ports());
        kprintf("threads:\t%" B_PRId32 " (%" B_PRId32 " max)\n",
                thread_used_threads(), thread_max_threads());
        kprintf("teams:\t\t%" B_PRId32 " (%" B_PRId32 " max)\n", team_used_teams(),
                team_max_teams());

        return 0;
}


//      #pragma mark - user notifications


class SystemNotificationService : private NotificationListener {
public:
        SystemNotificationService()
        {
                mutex_init(&fLock, "system notification service");
        }

        status_t Init()
        {
                status_t error = fTeamListeners.Init();
                if (error != B_OK)
                        return error;

                error = NotificationManager::Manager().AddListener("teams",
                        TEAM_ADDED | TEAM_REMOVED | TEAM_EXEC, *this);
                if (error != B_OK)
                        return error;

                error = NotificationManager::Manager().AddListener("threads",
                        THREAD_ADDED | THREAD_REMOVED | TEAM_EXEC, *this);
                if (error != B_OK)
                        return error;

                return B_OK;
        }

        status_t StartListening(int32 object, uint32 flags, port_id port,
                int32 token)
        {
                // check the parameters
                if ((object < 0 && object != -1) || port < 0)
                        return B_BAD_VALUE;

                if ((flags & B_WATCH_SYSTEM_ALL) == 0
                        || (flags & ~(uint32)B_WATCH_SYSTEM_ALL) != 0) {
                        return B_BAD_VALUE;
                }

                MutexLocker locker(fLock);

                // maybe the listener already exists
                ListenerList* listenerList;
                Listener* listener = _FindListener(object, port, token, listenerList);
                if (listener != NULL) {
                        // just add the new flags
                        listener->flags |= flags;
                        return B_OK;
                }

                // create a new listener
                listener = new(std::nothrow) Listener;
                if (listener == NULL)
                        return B_NO_MEMORY;
                ObjectDeleter<Listener> listenerDeleter(listener);

                listener->port = port;
                listener->token = token;
                listener->flags = flags;

                // if there's no list yet, create a new list
                if (listenerList == NULL) {
                        listenerList = new(std::nothrow) ListenerList;
                        if (listenerList == NULL)
                                return B_NO_MEMORY;

                        listenerList->object = object;

                        fTeamListeners.Insert(listenerList);
                }

                listener->list = listenerList;
                listenerList->listeners.Add(listener);
                listenerDeleter.Detach();

                team_associate_data(listener);

                return B_OK;
        }

        status_t StopListening(int32 object, uint32 flags, port_id port,
                int32 token)
        {
                MutexLocker locker(fLock);

                // find the listener
                ListenerList* listenerList;
                Listener* listener = _FindListener(object, port, token, listenerList);
                if (listener == NULL)
                        return B_ENTRY_NOT_FOUND;

                // clear the given flags
                listener->flags &= ~flags;

                if (listener->flags != 0)
                        return B_OK;

                team_dissociate_data(listener);
                _RemoveListener(listener);

                return B_OK;
        }

private:
        struct ListenerList;

        struct Listener : AssociatedData {
                DoublyLinkedListLink<Listener>  listLink;
                ListenerList*                                   list;
                port_id                                                 port;
                int32                                                   token;
                uint32                                                  flags;

                virtual void OwnerDeleted(AssociatedDataOwner* owner);
        };

        friend struct Listener;

        struct ListenerList {
                typedef DoublyLinkedList<Listener,
                        DoublyLinkedListMemberGetLink<Listener, &Listener::listLink> > List;

                ListenerList*   hashNext;
                List                    listeners;
                int32                   object;
        };

        struct ListenerHashDefinition {
                typedef int32                   KeyType;
                typedef ListenerList    ValueType;

                size_t HashKey(int32 key) const
                {
                        return key;
                }

                size_t Hash(const ListenerList* value) const
                {
                        return HashKey(value->object);
                }

                bool Compare(int32 key, const ListenerList* value) const
                {
                        return value->object == key;
                }

                ListenerList*& GetLink(ListenerList* value) const
                {
                        return value->hashNext;
                }
        };

        typedef BOpenHashTable<ListenerHashDefinition> ListenerHash;

private:
        virtual void EventOccurred(NotificationService& service,
                const KMessage* event)
        {
                MutexLocker locker(fLock);

                int32 eventCode;
                int32 teamID = 0;
                if (event->FindInt32("event", &eventCode) != B_OK
                        || event->FindInt32("team", &teamID) != B_OK) {
                        return;
                }

                int32 object;
                uint32 opcode;
                uint32 flags;

                // translate the event
                if (event->What() == TEAM_MONITOR) {
                        switch (eventCode) {
                                case TEAM_ADDED:
                                        opcode = B_TEAM_CREATED;
                                        flags = B_WATCH_SYSTEM_TEAM_CREATION;
                                        break;
                                case TEAM_REMOVED:
                                        opcode = B_TEAM_DELETED;
                                        flags = B_WATCH_SYSTEM_TEAM_DELETION;
                                        break;
                                case TEAM_EXEC:
                                        opcode = B_TEAM_EXEC;
                                        flags = B_WATCH_SYSTEM_TEAM_CREATION
                                                | B_WATCH_SYSTEM_TEAM_DELETION;
                                        break;
                                default:
                                        return;
                        }

                        object = teamID;
                } else if (event->What() == THREAD_MONITOR) {
                        if (event->FindInt32("thread", &object) != B_OK)
                                return;

                        switch (eventCode) {
                                case THREAD_ADDED:
                                        opcode = B_THREAD_CREATED;
                                        flags = B_WATCH_SYSTEM_THREAD_CREATION;
                                        break;
                                case THREAD_REMOVED:
                                        opcode = B_THREAD_DELETED;
                                        flags = B_WATCH_SYSTEM_THREAD_DELETION;
                                        break;
                                case THREAD_NAME_CHANGED:
                                        opcode = B_THREAD_NAME_CHANGED;
                                        flags = B_WATCH_SYSTEM_THREAD_PROPERTIES;
                                        break;
                                default:
                                        return;
                        }
                } else
                        return;

                // find matching listeners
                messaging_target targets[kMaxMessagingTargetCount];
                int32 targetCount = 0;

                _AddTargets(fTeamListeners.Lookup(teamID), flags, targets,
                        targetCount, object, opcode);
                _AddTargets(fTeamListeners.Lookup(-1), flags, targets, targetCount,
                        object, opcode);

                // send the message
                if (targetCount > 0)
                        _SendMessage(targets, targetCount, object, opcode);
        }

        void _AddTargets(ListenerList* listenerList, uint32 flags,
                messaging_target* targets, int32& targetCount, int32 object,
                uint32 opcode)
        {
                if (listenerList == NULL)
                        return;

                for (ListenerList::List::Iterator it
                                = listenerList->listeners.GetIterator();
                        Listener* listener = it.Next();) {
                        if ((listener->flags & flags) == 0)
                                continue;

                        // array is full -- need to flush it first
                        if (targetCount == kMaxMessagingTargetCount) {
                                _SendMessage(targets, targetCount, object, opcode);
                                targetCount = 0;
                        }

                        // add the listener
                        targets[targetCount].port = listener->port;
                        targets[targetCount++].token = listener->token;
                }
        }

        void _SendMessage(messaging_target* targets, int32 targetCount,
                int32 object, uint32 opcode)
        {
                // prepare the message
                char buffer[128];
                KMessage message;
                message.SetTo(buffer, sizeof(buffer), B_SYSTEM_OBJECT_UPDATE);
                message.AddInt32("opcode", opcode);
                if (opcode < B_THREAD_CREATED)
                        message.AddInt32("team", object);
                else
                        message.AddInt32("thread", object);

                // send it
                send_message(message.Buffer(), message.ContentSize(), targets,
                        targetCount);
        }

        Listener* _FindListener(int32 object, port_id port, int32 token,
                ListenerList*& _listenerList)
        {
                _listenerList = fTeamListeners.Lookup(object);
                if (_listenerList == NULL)
                        return NULL;

                for (ListenerList::List::Iterator it
                                = _listenerList->listeners.GetIterator();
                        Listener* listener = it.Next();) {
                        if (listener->port == port && listener->token == token)
                                return listener;
                }

                return NULL;
        }

        void _RemoveObsoleteListener(Listener* listener)
        {
                MutexLocker locker(fLock);
                _RemoveListener(listener);
        }

        void _RemoveListener(Listener* listener)
        {
                // no flags anymore -- remove the listener
                ListenerList* listenerList = listener->list;
                listenerList->listeners.Remove(listener);
                listener->ReleaseReference();

                if (listenerList->listeners.IsEmpty()) {
                        // no listeners in the list anymore -- remove the list from the hash
                        // table
                        fTeamListeners.Remove(listenerList);
                        delete listenerList;
                }
        }

private:
        static const int32 kMaxMessagingTargetCount = 8;

        mutex                   fLock;
        ListenerHash    fTeamListeners;
};

static SystemNotificationService sSystemNotificationService;


void
SystemNotificationService::Listener::OwnerDeleted(AssociatedDataOwner* owner)
{
        sSystemNotificationService._RemoveObsoleteListener(this);
}


//      #pragma mark - private functions


static void
count_topology_nodes(const cpu_topology_node* node, uint32& count)
{
        count++;
        for (int32 i = 0; i < node->children_count; i++)
                count_topology_nodes(node->children[i], count);
}


static int32
get_logical_processor(const cpu_topology_node* node)
{
        while (node->level != CPU_TOPOLOGY_SMT) {
                ASSERT(node->children_count > 0);
                node = node->children[0];
        }

        return node->id;
}


static cpu_topology_node_info*
generate_topology_array(cpu_topology_node_info* topology,
        const cpu_topology_node* node, uint32& count)
{
        if (count == 0)
                return topology;

        static const topology_level_type mapTopologyLevels[] = { B_TOPOLOGY_SMT,
                B_TOPOLOGY_CORE, B_TOPOLOGY_PACKAGE, B_TOPOLOGY_ROOT };

        STATIC_ASSERT(sizeof(mapTopologyLevels) / sizeof(topology_level_type)
                == CPU_TOPOLOGY_LEVELS + 1);

        topology->id = node->id;
        topology->level = node->level;
        topology->type = mapTopologyLevels[node->level];

        arch_fill_topology_node(topology, get_logical_processor(node));

        count--;
        topology++;
        for (int32 i = 0; i < node->children_count && count > 0; i++)
                topology = generate_topology_array(topology, node->children[i], count);
        return topology;
}


//      #pragma mark -


status_t
get_system_info(system_info* info)
{
        memset(info, 0, sizeof(system_info));

        info->boot_time = rtc_boot_time();
        info->cpu_count = smp_get_num_cpus();

        vm_page_get_stats(info);
        vm_get_info(info);

        info->used_threads = thread_used_threads();
        info->max_threads = thread_max_threads();
        info->used_teams = team_used_teams();
        info->max_teams = team_max_teams();
        info->used_ports = port_used_ports();
        info->max_ports = port_max_ports();
        info->used_sems = sem_used_sems();
        info->max_sems = sem_max_sems();

        info->kernel_version = kKernelVersion;
        strlcpy(info->kernel_name, kKernelName, B_FILE_NAME_LENGTH);
        strlcpy(info->kernel_build_date, __DATE__, B_OS_NAME_LENGTH);
        strlcpy(info->kernel_build_time, __TIME__, B_OS_NAME_LENGTH);
        info->abi = B_HAIKU_ABI;

        return B_OK;
}


typedef struct {
        bigtime_t       active_time;
        bool            enabled;
} beta2_cpu_info;


extern "C" status_t
__get_cpu_info(uint32 firstCPU, uint32 cpuCount, beta2_cpu_info* beta2_info)
{
        cpu_info info[cpuCount];
        status_t err = _get_cpu_info_etc(firstCPU, cpuCount, info, sizeof(cpu_info));
        if (err == B_OK) {
                for (uint32 i = 0; i < cpuCount; i++) {
                        beta2_info[i].active_time = info[i].active_time;
                        beta2_info[i].enabled = info[i].enabled;
                }
        }
        return err;
}


status_t
_get_cpu_info_etc(uint32 firstCPU, uint32 cpuCount, cpu_info* info, size_t size)
{
        if (cpuCount == 0)
                return B_OK;
        if (size != sizeof(cpu_info))
                return B_BAD_VALUE;
        if (firstCPU >= (uint32)smp_get_num_cpus())
                return B_BAD_VALUE;

        const uint32 endCPU = firstCPU + std::min(cpuCount, smp_get_num_cpus() - firstCPU);

        // This function is called very often from userland by applications
        // that display CPU usage information, so we want to keep this as
        // optimized and touch as little as possible. Hence, we avoid use
        // of an allocated temporary buffer.

        cpu_info localInfo[8];
        for (uint32 cpuIdx = firstCPU; cpuIdx < endCPU; ) {
                uint32 localIdx;
                for (localIdx = 0; cpuIdx < endCPU && localIdx < B_COUNT_OF(localInfo);
                                cpuIdx++, localIdx++) {
                        localInfo[localIdx].active_time = cpu_get_active_time(cpuIdx);
                        localInfo[localIdx].enabled = !gCPU[cpuIdx].disabled;
                        localInfo[localIdx].current_frequency = cpu_frequency(cpuIdx);
                }

                if (user_memcpy(info, localInfo, sizeof(cpu_info) * localIdx) != B_OK)
                        return B_BAD_ADDRESS;
                info += localIdx;
        }

        return B_OK;
}


status_t
system_info_init(struct kernel_args *args)
{
        add_debugger_command("info", &dump_info, "System info");

        return arch_system_info_init(args);
}


status_t
system_notifications_init()
{
        new (&sSystemNotificationService) SystemNotificationService;

        status_t error = sSystemNotificationService.Init();
        if (error != B_OK) {
                panic("system_info_init(): Failed to init system notification service");
                return error;
        }

        return B_OK;
}


//      #pragma mark -


status_t
_user_get_system_info(system_info* userInfo)
{
        if (userInfo == NULL || !IS_USER_ADDRESS(userInfo))
                return B_BAD_ADDRESS;

        system_info info;
        status_t status = get_system_info(&info);
        if (status == B_OK) {
                if (user_memcpy(userInfo, &info, sizeof(system_info)) < B_OK)
                        return B_BAD_ADDRESS;

                return B_OK;
        }

        return status;
}


status_t
_user_get_cpu_info(uint32 firstCPU, uint32 cpuCount, cpu_info* userInfo)
{
        if (userInfo == NULL || !IS_USER_ADDRESS(userInfo))
                return B_BAD_ADDRESS;

        return _get_cpu_info_etc(firstCPU, cpuCount, userInfo, sizeof(cpu_info));
}


status_t
_user_get_cpu_topology_info(cpu_topology_node_info* topologyInfos,
        uint32* topologyInfoCount)
{
        if (topologyInfoCount == NULL || !IS_USER_ADDRESS(topologyInfoCount))
                return B_BAD_ADDRESS;

        const cpu_topology_node* node = get_cpu_topology();

        uint32 count = 0;
        count_topology_nodes(node, count);

        if (topologyInfos == NULL)
                return user_memcpy(topologyInfoCount, &count, sizeof(uint32));
        else if (!IS_USER_ADDRESS(topologyInfos))
                return B_BAD_ADDRESS;

        uint32 userCount;
        status_t error = user_memcpy(&userCount, topologyInfoCount, sizeof(uint32));
        if (error != B_OK)
                return error;
        if (userCount == 0)
                return B_OK;
        count = std::min(count, userCount);

        cpu_topology_node_info* topology
                = new(std::nothrow) cpu_topology_node_info[count];
        if (topology == NULL)
                return B_NO_MEMORY;
        ArrayDeleter<cpu_topology_node_info> _(topology);
        memset(topology, 0, sizeof(cpu_topology_node_info) * count);

        uint32 nodesLeft = count;
        generate_topology_array(topology, node, nodesLeft);
        ASSERT(nodesLeft == 0);

        error = user_memcpy(topologyInfos, topology,
                sizeof(cpu_topology_node_info) * count);
        if (error != B_OK)
                return error;
        return user_memcpy(topologyInfoCount, &count, sizeof(uint32));
}


status_t
_user_start_watching_system(int32 object, uint32 flags, port_id port,
        int32 token)
{
        return sSystemNotificationService.StartListening(object, flags, port,
                token);
}


status_t
_user_stop_watching_system(int32 object, uint32 flags, port_id port,
        int32 token)
{
        return sSystemNotificationService.StopListening(object, flags, port, token);
}