[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Xen-devel] [RFC PATCH 29/31] xen/arm: Introduce CPUFreq Interface component



Hi Stefano

On Wed, Dec 6, 2017 at 12:25 AM, Stefano Stabellini
<sstabellini@xxxxxxxxxx> wrote:
> On Thu, 9 Nov 2017, Oleksandr Tyshchenko wrote:
>> From: Oleksandr Tyshchenko <oleksandr_tyshchenko@xxxxxxxx>
>>
>> This patch adds an interface component which performs following steps:
>> 1. Initialize everything needed SCPI based CPUFreq driver to be functional
>>    (SCPI Message protocol, mailbox to communicate with SCP, etc).
>>    Also preliminary check if SCPI DVFS clock nodes offered by SCP are
>>    present in a device tree.
>> 2. Register SCPI based CPUFreq driver.
>> 3. Populate CPUs. Get DVFS info (OPP list and the latency information)
>>    for all DVFS capable CPUs using SCPI protocol, convert these capabilities
>>    into PM data the CPUFreq framework expects to see followed by
>>    uploading it.
>>
>> Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@xxxxxxxx>
>> CC: Stefano Stabellini <sstabellini@xxxxxxxxxx>
>> CC: Julien Grall <julien.grall@xxxxxxxxxx>
>> ---
>>  xen/arch/arm/cpufreq/cpufreq_if.c | 522 
>> ++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 522 insertions(+)
>>  create mode 100644 xen/arch/arm/cpufreq/cpufreq_if.c
>>
>> diff --git a/xen/arch/arm/cpufreq/cpufreq_if.c 
>> b/xen/arch/arm/cpufreq/cpufreq_if.c
>> new file mode 100644
>> index 0000000..2451d00
>> --- /dev/null
>> +++ b/xen/arch/arm/cpufreq/cpufreq_if.c
>> @@ -0,0 +1,522 @@
>> +/*
>> + * xen/arch/arm/cpufreq/cpufreq_if.c
>> + *
>> + * CPUFreq interface component
>> + *
>> + * Oleksandr Tyshchenko <oleksandr_tyshchenko@xxxxxxxx>
>> + * Copyright (c) 2017 EPAM Systems.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 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, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <xen/device_tree.h>
>> +#include <xen/err.h>
>> +#include <xen/sched.h>
>> +#include <xen/cpufreq.h>
>> +#include <xen/pmstat.h>
>> +#include <xen/guest_access.h>
>> +
>> +#include "scpi_protocol.h"
>> +
>> +/*
>> + * TODO:
>> + * 1. Add __init to required funcs
>> + * 2. Put get_cpu_device() into common place
>> + */
>> +
>> +static struct scpi_ops *scpi_ops;
>> +
>> +extern int scpi_cpufreq_register_driver(void);
>> +
>> +#define dev_name(dev) dt_node_full_name(dev_to_dt(dev))
>> +
>> +struct device *get_cpu_device(unsigned int cpu)
>> +{
>> +    if ( cpu < nr_cpu_ids && cpu_possible(cpu) )
>> +        return dt_to_dev(cpu_dt_nodes[cpu]);
>> +    else
>> +        return NULL;
>> +}
>> +
>> +static bool is_dvfs_capable(unsigned int cpu)
>> +{
>> +    static const struct dt_device_match scpi_dvfs_clock_match[] =
>> +    {
>> +        DT_MATCH_COMPATIBLE("arm,scpi-dvfs-clocks"),
>> +        { /* sentinel */ },
>> +    };
>> +    struct device *cpu_dev;
>> +    struct dt_phandle_args clock_spec;
>> +    struct scpi_dvfs_info *info;
>> +    u32 domain;
>> +    int i, ret, count;
>> +
>> +    cpu_dev = get_cpu_device(cpu);
>> +    if ( !cpu_dev )
>> +    {
>> +        printk("cpu%d: failed to get device\n", cpu);
>> +        return false;
>> +    }
>> +
>> +    /* First of all find a clock node this CPU is a consumer of */
>> +    ret = dt_parse_phandle_with_args(cpu_dev->of_node,
>> +                                     "clocks",
>> +                                     "#clock-cells",
>> +                                     0,
>> +                                     &clock_spec);
>> +    if ( ret )
>> +    {
>> +        printk("cpu%d: failed to get clock node\n", cpu);
>> +        return false;
>> +    }
>> +
>> +    /* Make sure it is an available DVFS clock node */
>> +    if ( !dt_match_node(scpi_dvfs_clock_match, clock_spec.np) ||
>> +         !dt_device_is_available(clock_spec.np) )
>> +    {
>> +        printk("cpu%d: clock node '%s' is either non-DVFS or 
>> non-available\n",
>> +               cpu, dev_name(&clock_spec.np->dev));
>> +        return false;
>> +    }
>> +
>> +    /*
>> +     * Actually we already have a power domain id this CPU belongs to,
>> +     * it is a stored in args[0] CPU clock specifier, so we could ask SCP
>> +     * to provide its DVFS info. But we want to dig a little bit deeper
>> +     * to make sure that everything is correct.
>> +     */
>> +
>> +    /* Check how many clock ids a DVFS clock node has */
>> +    ret = dt_property_count_elems_of_size(clock_spec.np,
>> +                                          "clock-indices",
>> +                                          sizeof(u32));
>> +    if ( ret < 0 )
>> +    {
>> +        printk("cpu%d: failed to get clock-indices count in '%s'\n",
>> +               cpu, dev_name(&clock_spec.np->dev));
>> +        return false;
>> +    }
>> +    count = ret;
>> +
>> +    /* Check if a clock id the CPU clock specifier points to is present */
>> +    for ( i = 0; i < count; i++ )
>> +    {
>> +        ret = dt_property_read_u32_index(clock_spec.np,
>> +                                         "clock-indices",
>> +                                         i,
>> +                                         &domain);
>> +        if ( ret )
>> +        {
>> +            printk("cpu%d: failed to get clock index in '%s'\n",
>> +                   cpu, dev_name(&clock_spec.np->dev));
>> +            return false;
>> +        }
>> +
>> +        /* Match found */
>> +        if ( clock_spec.args[0] == domain )
>> +            break;
>> +    }
>> +
>> +    if ( i == count )
>> +    {
>> +        printk("cpu%d: failed to find matching clk_id (pd) %d\n",
>> +               cpu, clock_spec.args[0]);
>> +        return false;
>> +    }
>> +
>> +    /*
>> +     * Check if a SCP is aware of this power domain. SCPI Message protocol
>> +     * driver will populate power domain's DVFS info then.
>> +     */
>> +    info = scpi_ops->dvfs_get_info(domain);
>> +    if ( IS_ERR(info) )
>> +    {
>> +        printk("cpu%d: failed to get DVFS info of pd%u\n", cpu, domain);
>> +        return false;
>> +    }
>> +
>> +    printk(XENLOG_DEBUG "cpu%d: is DVFS capable, belongs to pd%u\n",
>> +           cpu, domain);
>> +
>> +    return true;
>> +}
>> +
>> +static int get_sharing_cpus(unsigned int cpu, cpumask_t *mask)
>> +{
>> +    struct device *cpu_dev = get_cpu_device(cpu), *tcpu_dev;
>> +    unsigned int tcpu;
>> +    int domain, tdomain;
>> +
>> +    BUG_ON(!cpu_dev);
>> +
>> +    domain = scpi_ops->device_domain_id(cpu_dev);
>> +    if ( domain < 0 )
>> +        return domain;
>> +
>> +    cpumask_clear(mask);
>> +    cpumask_set_cpu(cpu, mask);
>> +
>> +    for_each_online_cpu( tcpu )
>> +    {
>> +        if ( tcpu == cpu )
>> +            continue;
>> +
>> +        tcpu_dev = get_cpu_device(tcpu);
>> +        if ( !tcpu_dev )
>> +            continue;
>> +
>> +        tdomain = scpi_ops->device_domain_id(tcpu_dev);
>> +        if ( tdomain == domain )
>> +            cpumask_set_cpu(tcpu, mask);
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int get_transition_latency(struct device *cpu_dev)
>> +{
>> +    return scpi_ops->get_transition_latency(cpu_dev);
>> +}
>> +
>> +static struct scpi_dvfs_info *get_dvfs_info(struct device *cpu_dev)
>> +{
>> +    int domain;
>> +
>> +    domain = scpi_ops->device_domain_id(cpu_dev);
>> +    if ( domain < 0 )
>> +        return ERR_PTR(-EINVAL);
>> +
>> +    return scpi_ops->dvfs_get_info(domain);
>> +}
>> +
>> +static int init_cpufreq_table(unsigned int cpu,
>> +                              struct cpufreq_frequency_table **table)
>> +{
>> +    struct cpufreq_frequency_table *freq_table = NULL;
>> +    struct device *cpu_dev = get_cpu_device(cpu);
>> +    struct scpi_dvfs_info *info;
>> +    struct scpi_opp *opp;
>> +    int i;
>> +
>> +    BUG_ON(!cpu_dev);
>> +
>> +    info = get_dvfs_info(cpu_dev);
>> +    if ( IS_ERR(info) )
>> +        return PTR_ERR(info);
>> +
>> +    if ( !info->opps )
>> +        return -EIO;
>> +
>> +    freq_table = xzalloc_array(struct cpufreq_frequency_table, info->count 
>> + 1);
>> +    if ( !freq_table )
>> +        return -ENOMEM;
>> +
>> +    for ( opp = info->opps, i = 0; i < info->count; i++, opp++ )
>> +    {
>> +        freq_table[i].index = i;
>> +        /* Convert Hz -> kHz */
>> +        freq_table[i].frequency = opp->freq / 1000;
>> +    }
>> +
>> +    freq_table[i].index = i;
>> +    freq_table[i].frequency = CPUFREQ_TABLE_END;
>> +
>> +    *table = &freq_table[0];
>> +
>> +    return 0;
>> +}
>> +
>> +static void free_cpufreq_table(struct cpufreq_frequency_table **table)
>> +{
>> +    if ( !table )
>> +        return;
>> +
>> +    xfree(*table);
>> +    *table = NULL;
>> +}
>> +
>> +static int upload_cpufreq_data(cpumask_t *mask,
>> +                               struct cpufreq_frequency_table *table)
>> +{
>> +    struct xen_processor_performance *perf;
>> +    struct xen_processor_px *states;
>> +    uint32_t platform_limit = 0, state_count = 0;
>> +    unsigned int max_freq = 0, prev_freq = 0, cpu = cpumask_first(mask);
>> +    int i, latency, ret = 0;
>> +
>> +    perf = xzalloc(struct xen_processor_performance);
>> +    if ( !perf )
>> +        return -ENOMEM;
>> +
>> +    /* Check frequency table and find max frequency */
>> +    for ( i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++ )
>> +    {
>> +        unsigned int freq = table[i].frequency;
>> +
>> +        if ( freq == CPUFREQ_ENTRY_INVALID )
>> +            continue;
>> +
>> +        if ( table[i].index != state_count || freq <= prev_freq )
>> +        {
>> +            printk("cpu%d: frequency table format error\n", cpu);
>> +            ret = -EINVAL;
>> +            goto out;
>> +        }
>> +
>> +        prev_freq = freq;
>> +        state_count++;
>> +        if ( freq > max_freq )
>> +            max_freq = freq;
>> +    }
>> +
>> +    /*
>> +     * The frequency table we have is just a temporary place for storing
>> +     * provided by SCP DVFS info. Create performance states array.
>> +     */
>> +    if ( !state_count )
>> +    {
>> +        printk("cpu%d: no available performance states\n", cpu);
>> +        ret = -EINVAL;
>> +        goto out;
>> +    }
>> +
>> +    states = xzalloc_array(struct xen_processor_px, state_count);
>> +    if ( !states )
>> +    {
>> +        ret = -ENOMEM;
>> +        goto out;
>> +    }
>> +
>> +    set_xen_guest_handle(perf->states, states);
>
> this is the bit that should go away

Yes. To add some glue I put references:

This patch must be reworked:
https://www.mail-archive.com/xen-devel@xxxxxxxxxxxxx/msg128410.html

Here we have started discussion how to rework it:
https://marc.info/?l=xen-devel&m=151250698607186

>
>
>> +    perf->state_count = state_count;
>> +
>> +    latency = get_transition_latency(get_cpu_device(cpu));
>> +
>> +    /* Performance states must start from higher values */
>> +    for ( i = 0; (table[i].frequency != CPUFREQ_TABLE_END); i++ )
>> +    {
>> +        unsigned int freq = table[i].frequency;
>> +        unsigned int index = state_count - 1 - table[i].index;
>> +
>> +        if ( freq == CPUFREQ_ENTRY_INVALID )
>> +            continue;
>> +
>> +        if ( freq == max_freq )
>> +            platform_limit = index;
>> +
>> +        /* Convert kHz -> MHz */
>> +        states[index].core_frequency = freq / 1000;
>> +        /* Convert ns -> us */
>> +        states[index].transition_latency = DIV_ROUND_UP(latency, 1000);
>
> Why are we using DIV_ROUND_UP here and not in all the other frequency
> conversions?

I decided to use DIV_ROUND_UP here, since the latency theoretically
might be less then 1000 ns and
we might end up with 0 us using the simple division.

>
>
>> +    }
>> +
>> +    perf->flags = XEN_PX_DATA; /* all info in a one-shot */
>
> Please use existing flags

Yes, sure. As we have already agreed here:
https://marc.info/?l=xen-devel&m=151250698607186

>
>
>> +    perf->platform_limit = platform_limit;
>> +    perf->shared_type = CPUFREQ_SHARED_TYPE_ANY;
>> +    perf->domain_info.domain = cpumask_first(mask);
>> +    perf->domain_info.num_processors = cpumask_weight(mask);
>> +
>> +    /* Iterate through all CPUs which are on the same boat */
>> +    for_each_cpu( cpu, mask )
>> +    {
>> +        ret = set_px_pminfo(cpu, perf);
>> +        if ( ret )
>> +        {
>> +            printk("cpu%d: failed to set Px states (%d)\n", cpu, ret);
>> +            break;
>> +        }
>> +
>> +        printk(XENLOG_DEBUG "cpu%d: set Px states\n", cpu);
>> +    }
>> +
>> +    xfree(states);
>> +out:
>> +    xfree(perf);
>> +
>> +    return ret;
>> +}
>> +
>> +static int __init scpi_cpufreq_postinit(void)
>> +{
>> +    struct cpufreq_frequency_table *freq_table = NULL;
>> +    cpumask_t processed_cpus, shared_cpus;
>> +    unsigned int cpu;
>> +    int ret = -ENODEV;
>> +
>> +    cpumask_clear(&processed_cpus);
>> +
>> +    for_each_online_cpu( cpu )
>> +    {
>> +        if ( cpumask_test_cpu(cpu, &processed_cpus) )
>> +            continue;
>> +
>> +        if ( !is_dvfs_capable(cpu) )
>> +            continue;
>> +
>> +        ret = get_sharing_cpus(cpu, &shared_cpus);
>> +        if ( ret )
>> +        {
>> +            printk("cpu%d: failed to get sharing cpumask (%d)\n", cpu, ret);
>> +            return ret;
>> +        }
>> +
>> +        BUG_ON(cpumask_empty(&shared_cpus));
>> +        cpumask_or(&processed_cpus, &processed_cpus, &shared_cpus);
>> +
>> +        /* Create intermediate frequency table */
>> +        ret = init_cpufreq_table(cpu, &freq_table);
>> +        if ( ret )
>> +        {
>> +            printk("cpu%d: failed to initialize frequency table (%d)\n",
>> +                   cpu, ret);
>> +            return ret;
>> +        }
>> +
>> +        ret = upload_cpufreq_data(&shared_cpus, freq_table);
>> +        /* Destroy intermediate frequency table */
>> +        free_cpufreq_table(&freq_table);
>> +        if ( ret )
>> +        {
>> +            printk("cpu%d: failed to upload cpufreq data (%d)\n", cpu, ret);
>> +            return ret;
>> +        }
>> +
>> +        printk(XENLOG_DEBUG "cpu%d: uploaded cpufreq data\n", cpu);
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static int __init scpi_cpufreq_preinit(void)
>> +{
>> +    struct dt_device_node *scpi, *clk, *dvfs_clk;
>> +    int ret;
>> +
>> +    /* Initialize SCPI Message protocol */
>> +    ret = scpi_init();
>> +    if ( ret )
>> +    {
>> +        printk("failed to initialize SCPI (%d)\n", ret);
>> +        return ret;
>> +    }
>> +
>> +    /* Sanity check */
>> +    if ( !get_scpi_ops() || !get_scpi_dev() )
>> +        return -ENXIO;
>> +
>> +    scpi = get_scpi_dev()->of_node;
>> +    scpi_ops = get_scpi_ops();
>> +
>> +    ret = -ENODEV;
>> +
>> +    /*
>> +     * Check for clock related nodes for now. But it might additional nodes,
>> +     * like thermal sensor, etc.
>> +     */
>> +    dt_for_each_child_node( scpi, clk )
>
> Wouldn't it make sense to have a proper:
>
> DT_DEVICE_START
> ...
> DT_DEVICE_END
>
> block and register the driver that way?

I am not sure that I got your question completely.
Which driver need to be registered in a such way?
If we had separate dt-related driver to manage clocks we would have to
register it in a proposed way.
Here we just iterating through all SCPI child in order to be sure that
DVFS clock sub-node is present.
Let say, preliminary check.

BTW, in a proposed way I register ARM SMC triggered mailbox driver:
https://www.mail-archive.com/xen-devel@xxxxxxxxxxxxx/msg128411.html
With adding new DEVICE_MAILBOX class:
https://www.mail-archive.com/xen-devel@xxxxxxxxxxxxx/msg128402.html

>
>
>> +    {
>> +        /*
>> +         * First of all there must be a container node which contains all
>> +         * clocks provided by SCP.
>> +         */
>> +        if ( !dt_device_is_compatible(clk, "arm,scpi-clocks") )
>> +            continue;
>> +
>> +        /*
>> +         * As we are interested in DVFS feature only, check for DVFS clock
>> +         * sub-node. At the current stage check for it presence only.
>> +         * Without it there is no point to register SCPI based CPUFreq. We 
>> will
>> +         * perform a thorough check later when populating DVFS clock 
>> consumers.
>> +         */
>> +        dt_for_each_child_node( clk, dvfs_clk )
>> +        {
>> +            if ( !dt_device_is_compatible(dvfs_clk, "arm,scpi-dvfs-clocks") 
>> )
>> +                continue;
>> +
>> +            return 0;
>> +        }
>> +
>> +        break;
>> +    }
>> +
>> +    printk("failed to find SCPI DVFS clocks (%d)\n", ret);
>> +
>> +    return ret;
>> +}
>> +
>> +/* TODO Implement me */
>
> :-)
>
>
>> +static void scpi_cpufreq_deinit(void)
>> +{
>> +
>> +}
>> +
>> +static int __init cpufreq_driver_init(void)
>> +{
>> +    int ret;
>> +
>> +    if ( cpufreq_controller != FREQCTL_xen )
>> +        return 0;
>> +
>> +    /*
>> +     * Initialize everything needed SCPI based CPUFreq driver to be 
>> functional
>> +     * (SCPI Message protocol, mailbox to communicate with SCP, etc).
>> +     * Also preliminary check if SCPI DVFS clock nodes offered by SCP are
>> +     * present in a device tree.
>> +     */
>> +    ret = scpi_cpufreq_preinit();
>> +    if ( ret )
>> +        goto out;
>> +
>> +    /* Register SCPI based CPUFreq driver */
>> +    ret = scpi_cpufreq_register_driver();
>> +    if ( ret )
>> +        goto out;
>> +
>> +    /*
>> +     * Populate CPUs. Get DVFS info (OPP list and the latency information)
>> +     * for all DVFS capable CPUs using SCPI protocol, convert these 
>> capabilities
>> +     * into PM data the CPUFreq framework expects to see followed by
>> +     * uploading it.
>> +     *
>> +     * Actually it is almost the same PM data which hwdom uploads in case of
>> +     * x86 via platform hypercall after parsing ACPI tables. In our case we
>> +     * don't need hwdom to be involved in, since we already have everything 
>> in
>> +     * hand. Moreover, the hwdom doesn't even know anything about physical 
>> CPUs.
>> +     * Not completely sure that it is the best place to do so, but certainly
>> +     * it must be after driver registration.
>> +     */
>> +    ret = scpi_cpufreq_postinit();
>> +
>> +out:
>> +    if ( ret )
>> +    {
>> +        printk("failed to initialize SCPI based CPUFreq (%d)\n", ret);
>> +        scpi_cpufreq_deinit();
>> +        return ret;
>> +    }
>> +
>> +    printk("initialized SCPI based CPUFreq\n");
>> +
>> +    return 0;
>> +}
>> +__initcall(cpufreq_driver_init);
>> +
>> +/*
>> + * Local variables:
>> + * mode: C
>> + * c-file-style: "BSD"
>> + * c-basic-offset: 4
>> + * tab-width: 4
>> + * indent-tabs-mode: nil
>> + * End:
>> + */



-- 
Regards,

Oleksandr Tyshchenko

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.