Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions driver/hvisor_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ static long hvisor_ioctl(struct file *file, unsigned int ioctl,
break;
case HVISOR_ZONE_SHUTDOWN:
err = hvisor_call(HVISOR_HC_SHUTDOWN_ZONE, arg, 0);
if (err == 0)
scmi_release_zone((u32)arg);
break;
case HVISOR_ZONE_LIST:
err = hvisor_zone_list((zone_list_args_t __user *)arg);
Expand Down
89 changes: 86 additions & 3 deletions driver/virtio/scmi/clock.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "hvisor.h"
#include "server.h"
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
Expand All @@ -11,6 +12,74 @@ extern const char *__clk_get_name(const struct clk *clk);
static struct clk **clock_cache;
static u32 clock_max_num;

/* Per-zone enable accounting: clk_zone_en[zone][clock_id] counts how many
* times zone @zone enabled clock @clock_id through SCMI (rows allocated
* lazily). The kernel driver owns this state because it executes the clk
* ops and sees zone start/shutdown; zone shutdown / next zone start undo
* exactly the zone's own contributions. */
static u32 **clk_zone_en;
static DEFINE_MUTEX(clock_zone_lock);

static struct clk *clock_get_cached(u32 clock_id);

static u32 *clock_zone_row(u32 zone) {
u32 *row;

if (zone >= SCMI_MAX_ZONES || clock_max_num == 0)
return NULL;
row = clk_zone_en ? clk_zone_en[zone] : NULL;
if (!row) {
row = kcalloc(clock_max_num, sizeof(u32), GFP_KERNEL);
if (!row)
return NULL;
if (!clk_zone_en) {
clk_zone_en = kcalloc(SCMI_MAX_ZONES, sizeof(u32 *), GFP_KERNEL);
if (!clk_zone_en) {
kfree(row);
return NULL;
}
}
clk_zone_en[zone] = row;
}
return row;
}

void clock_zone_adjust(u32 zone, u32 clk, bool enable) {
u32 *row;

mutex_lock(&clock_zone_lock);
row = clock_zone_row(zone);
if (row && clk < clock_max_num) {
if (enable)
row[clk]++;
else if (row[clk] > 0)
row[clk]--;
}
mutex_unlock(&clock_zone_lock);
}

void clock_zone_release(u32 zone) {
u32 *row;
u32 clk;

mutex_lock(&clock_zone_lock);
row = (zone < SCMI_MAX_ZONES && clk_zone_en) ? clk_zone_en[zone] : NULL;
if (!row) {
mutex_unlock(&clock_zone_lock);
return;
}
for (clk = 0; clk < clock_max_num; clk++) {
while (row[clk] > 0) {
struct clk *c = clock_get_cached(clk);
if (IS_ERR(c))
break;
clk_disable_unprepare(c);
row[clk]--;
}
}
mutex_unlock(&clock_zone_lock);
}

int clock_init(void) {
struct device_node *np;
int c;
Expand Down Expand Up @@ -161,6 +230,14 @@ void clock_ctrl_finish(void) {
}

out:
if (clk_zone_en) {
u32 z;

for (z = 0; z < SCMI_MAX_ZONES; z++)
kfree(clk_zone_en[z]);
kfree(clk_zone_en);
clk_zone_en = NULL;
}
kfree(clock_cache);
clock_cache = NULL;
clock_max_num = 0;
Expand Down Expand Up @@ -230,9 +307,15 @@ int hvisor_scmi_clock_ioctl(struct hvisor_scmi_clock_args __user *user_args) {
return -EFAULT;
return 0;
}
case HVISOR_SCMI_CLOCK_CONFIG_SET:
return set_clock_config(args.u.clock_config_info.clock_id,
args.u.clock_config_info.config);
case HVISOR_SCMI_CLOCK_CONFIG_SET: {
u32 clk = args.u.clock_config_info.clock_id;
u32 cfg = args.u.clock_config_info.config;
int r = set_clock_config(clk, cfg);

if (r == 0)
clock_zone_adjust(args.zone_id, clk, cfg & 1);
return r;
}
case HVISOR_SCMI_CLOCK_NAME_GET: {
char name[64];
int ret = get_clock_name(args.u.clock_name_info.clock_id, name);
Expand Down
81 changes: 79 additions & 2 deletions driver/virtio/scmi/power.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "hvisor.h"
#include "server.h"
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
Expand All @@ -16,6 +17,69 @@ struct power_domain_dev {
static struct power_domain_dev *power_devices;
static u32 power_max_num;

/* Per-zone power-on accounting (see clock.c for rationale). */
static u32 **pwr_zone_on;
static DEFINE_MUTEX(power_zone_lock);

static int power_domain_state_set(u32 domain_id, u32 power_state);

static u32 *power_zone_row(u32 zone) {
u32 *row;

if (zone >= SCMI_MAX_ZONES || power_max_num == 0)
return NULL;
row = pwr_zone_on ? pwr_zone_on[zone] : NULL;
if (!row) {
row = kcalloc(power_max_num, sizeof(u32), GFP_KERNEL);
if (!row)
return NULL;
if (!pwr_zone_on) {
pwr_zone_on = kcalloc(SCMI_MAX_ZONES, sizeof(u32 *), GFP_KERNEL);
if (!pwr_zone_on) {
kfree(row);
return NULL;
}
}
pwr_zone_on[zone] = row;
}
return row;
}

void power_zone_adjust(u32 zone, u32 domain, bool on) {
u32 *row;

mutex_lock(&power_zone_lock);
row = power_zone_row(zone);
if (row && domain < power_max_num) {
if (on)
row[domain]++;
else if (row[domain] > 0)
row[domain]--;
}
mutex_unlock(&power_zone_lock);
}

void power_zone_release(u32 zone) {
u32 *row;
u32 domain;

mutex_lock(&power_zone_lock);
row = (zone < SCMI_MAX_ZONES && pwr_zone_on) ? pwr_zone_on[zone] : NULL;
if (!row) {
mutex_unlock(&power_zone_lock);
return;
}
for (domain = 0; domain < power_max_num; domain++) {
while (row[domain] > 0) {
if (power_domain_state_set(domain, SCMI_POWER_STATE_GENERIC_OFF) !=
0)
break;
row[domain]--;
}
}
mutex_unlock(&power_zone_lock);
}

/* Control platform device - carrier for the hvisor DT node */
static struct platform_device *ctrl_pdev;

Expand Down Expand Up @@ -103,6 +167,14 @@ void power_ctrl_finish(void) {
power_devices = NULL;
power_max_num = 0;
}
if (pwr_zone_on) {
u32 z;

for (z = 0; z < SCMI_MAX_ZONES; z++)
kfree(pwr_zone_on[z]);
kfree(pwr_zone_on);
pwr_zone_on = NULL;
}

if (ctrl_pdev) {
platform_device_unregister(ctrl_pdev);
Expand Down Expand Up @@ -225,8 +297,13 @@ int hvisor_scmi_power_ioctl(struct hvisor_scmi_power_args __user *user_args) {
if (ret < 0)
return ret;

return power_domain_state_set(domain_id,
args.u.power_state_info.power_state);
ret = power_domain_state_set(domain_id,
args.u.power_state_info.power_state);
if (ret == 0)
power_zone_adjust(args.zone_id, domain_id,
args.u.power_state_info.power_state ==
SCMI_POWER_STATE_GENERIC_ON);
return ret;
}
case HVISOR_SCMI_POWER_STATE_GET: {
u32 domain_id = args.u.power_state_info.domain_id;
Expand Down
10 changes: 10 additions & 0 deletions driver/virtio/scmi/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ void hvisor_put_node(void) {
extern void clock_ctrl_finish(void);
extern void reset_ctrl_finish(void);
extern void power_ctrl_finish(void);
extern void clock_zone_release(u32 zone);
extern void power_zone_release(u32 zone);

void scmi_release_zone(u32 zone) {
/* Clocks first (stops scanout/DMA, removing the stale-interrupt source),
* then power off the domains (the hardware reset). */
clock_zone_release(zone);
power_zone_release(zone);
pr_info("hvisor.ko: released scmi resources of zone %u\n", zone);
}

int hvisor_scmi_init(void) {
int ret;
Expand Down
13 changes: 13 additions & 0 deletions driver/virtio/scmi/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,17 @@ int hvisor_scmi_power_ioctl(struct hvisor_scmi_power_args __user *user_args);
/* Cleanup — called from hvisor_exit() to release SCMI resources */
void hvisor_scmi_cleanup(void);

/* Per-zone lifecycle (kernel owns it: the driver executes the clk/pd ops
* and sees zone start/shutdown synchronously). */
#define SCMI_MAX_ZONES 16

/* Record that zone @zone enabled/disabled clock @clk (enable=true -> +1). */
void clock_zone_adjust(u32 zone, u32 clk, bool enable);
/* Record that zone @zone powered @domain on/off (on=true -> +1). */
void power_zone_adjust(u32 zone, u32 domain, bool on);
/* Undo everything zone @zone recorded: disable its clocks, then power off
* its domains. Idempotent; safe to call before a zone starts (any recorded
* state then belongs to a dead instance) and after it is shut down. */
void scmi_release_zone(u32 zone);

#endif /* _HVISOR_SCMI_SERVER_H */
4 changes: 4 additions & 0 deletions include/hvisor.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ struct hvisor_load_image_args {
struct hvisor_scmi_clock_args {
__u32 subcmd; /* Subcommand ID */
__u32 data_len; /* Length of data buffer */
__u32 zone_id; /* Zone the request belongs to (for per-zone resource
* accounting/lifecycle in the kernel driver) */
union {
__u32 clock_count; /* For GET_COUNT */
struct {
Expand Down Expand Up @@ -192,6 +194,8 @@ struct hvisor_scmi_reset_args {
struct hvisor_scmi_power_args {
__u32 subcmd; /* Subcommand ID */
__u32 data_len; /* Length of data buffer */
__u32 zone_id; /* Zone the request belongs to (for per-zone resource
* accounting/lifecycle in the kernel driver) */
union {
__u32 power_count; /* For GET_COUNT */
struct {
Expand Down
1 change: 1 addition & 0 deletions tools/virtio/devices/scmi/clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ static int handle_clock_config_set(SCMIDev *dev, uint16_t token,
struct hvisor_scmi_clock_args args;
struct clock_config_info *config_info =
(struct clock_config_info *)&args.u.data;
args.zone_id = dev->zone_id;
config_info->clock_id = phys_id;
config_info->config = r->attributes;

Expand Down
1 change: 1 addition & 0 deletions tools/virtio/devices/scmi/power.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ static int handle_power_state_set(SCMIDev *dev, uint16_t token,
}

struct hvisor_scmi_power_args args;
args.zone_id = dev->zone_id;
args.u.power_state_info.domain_id = phys_id;
args.u.power_state_info.power_state = power_state;

Expand Down
1 change: 1 addition & 0 deletions tools/virtio/devices/scmi/virtio_scmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ static int virtio_scmi_do_init(VirtIODevice *vdev, const void *params) {
return -ENOMEM;
vdev->dev = dev;
}
dev->zone_id = vdev->zone_id;

return 0;
}
Expand Down
6 changes: 6 additions & 0 deletions tools/virtio/include/virtio_scmi.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ struct virtio_scmi_config {

typedef struct virtio_scmi_dev {
struct virtio_scmi_config config;

/* Zone this device serves; stamped on every scmi ioctl so the kernel
* driver can account resources per zone and release them when the zone
* is (re)started or shut down. */
uint32_t zone_id;

uint32_t *clock_ids; /* NULL = protocol not supported */
uint32_t clock_count;
uint32_t *reset_ids;
Expand Down
Loading