Commit 183d0202 authored by Benjamin Herrenschmidt's avatar Benjamin Herrenschmidt Committed by Paul Mackerras

[PATCH] ppc64: SMU partition recovery

This patch adds the ability to the SMU driver to recover missing
calibration partitions from the SMU chip itself. It also adds some
dynamic mecanism to /proc/device-tree so that new properties are visible
to userland.
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent 4350147a
......@@ -1974,14 +1974,29 @@ EXPORT_SYMBOL(get_property);
/*
* Add a property to a node
*/
void prom_add_property(struct device_node* np, struct property* prop)
int prom_add_property(struct device_node* np, struct property* prop)
{
struct property **next = &np->properties;
struct property **next;
prop->next = NULL;
while (*next)
write_lock(&devtree_lock);
next = &np->properties;
while (*next) {
if (strcmp(prop->name, (*next)->name) == 0) {
/* duplicate ! don't insert it */
write_unlock(&devtree_lock);
return -1;
}
next = &(*next)->next;
}
*next = prop;
write_unlock(&devtree_lock);
/* try to add to proc as well if it was initialized */
if (np->pde)
proc_device_tree_add_prop(np->pde, prop);
return 0;
}
/* I quickly hacked that one, check against spec ! */
......
......@@ -1165,7 +1165,7 @@ get_property(struct device_node *np, const char *name, int *lenp)
/*
* Add a property to a node
*/
void
int
prom_add_property(struct device_node* np, struct property* prop)
{
struct property **next = &np->properties;
......@@ -1174,6 +1174,8 @@ prom_add_property(struct device_node* np, struct property* prop)
while (*next)
next = &(*next)->next;
*next = prop;
return 0;
}
/* I quickly hacked that one, check against spec ! */
......
......@@ -31,6 +31,7 @@
#include <linux/initrd.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/module.h>
#include <asm/prom.h>
#include <asm/rtas.h>
......@@ -1865,17 +1866,32 @@ get_property(struct device_node *np, const char *name, int *lenp)
EXPORT_SYMBOL(get_property);
/*
* Add a property to a node
* Add a property to a node.
*/
void
int
prom_add_property(struct device_node* np, struct property* prop)
{
struct property **next = &np->properties;
struct property **next;
prop->next = NULL;
while (*next)
write_lock(&devtree_lock);
next = &np->properties;
while (*next) {
if (strcmp(prop->name, (*next)->name) == 0) {
/* duplicate ! don't insert it */
write_unlock(&devtree_lock);
return -1;
}
next = &(*next)->next;
}
*next = prop;
write_unlock(&devtree_lock);
/* try to add to proc as well if it was initialized */
if (np->pde)
proc_device_tree_add_prop(np->pde, prop);
return 0;
}
#if 0
......
......@@ -47,13 +47,13 @@
#include <asm/uaccess.h>
#include <asm/of_device.h>
#define VERSION "0.6"
#define VERSION "0.7"
#define AUTHOR "(c) 2005 Benjamin Herrenschmidt, IBM Corp."
#undef DEBUG_SMU
#ifdef DEBUG_SMU
#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
#define DPRINTK(fmt, args...) do { udbg_printf(KERN_DEBUG fmt , ##args); } while (0)
#else
#define DPRINTK(fmt, args...) do { } while (0)
#endif
......@@ -92,7 +92,7 @@ struct smu_device {
* for now, just hard code that
*/
static struct smu_device *smu;
static DECLARE_MUTEX(smu_part_access);
/*
* SMU driver low level stuff
......@@ -113,9 +113,11 @@ static void smu_start_cmd(void)
DPRINTK("SMU: starting cmd %x, %d bytes data\n", cmd->cmd,
cmd->data_len);
DPRINTK("SMU: data buffer: %02x %02x %02x %02x ...\n",
DPRINTK("SMU: data buffer: %02x %02x %02x %02x %02x %02x %02x %02x\n",
((u8 *)cmd->data_buf)[0], ((u8 *)cmd->data_buf)[1],
((u8 *)cmd->data_buf)[2], ((u8 *)cmd->data_buf)[3]);
((u8 *)cmd->data_buf)[2], ((u8 *)cmd->data_buf)[3],
((u8 *)cmd->data_buf)[4], ((u8 *)cmd->data_buf)[5],
((u8 *)cmd->data_buf)[6], ((u8 *)cmd->data_buf)[7]);
/* Fill the SMU command buffer */
smu->cmd_buf->cmd = cmd->cmd;
......@@ -440,7 +442,7 @@ int smu_present(void)
EXPORT_SYMBOL(smu_present);
int smu_init (void)
int __init smu_init (void)
{
struct device_node *np;
u32 *data;
......@@ -845,16 +847,154 @@ int smu_queue_i2c(struct smu_i2c_cmd *cmd)
return 0;
}
struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size)
/*
* Handling of "partitions"
*/
static int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len)
{
DECLARE_COMPLETION(comp);
unsigned int chunk;
struct smu_cmd cmd;
int rc;
u8 params[8];
/* We currently use a chunk size of 0xe. We could check the
* SMU firmware version and use bigger sizes though
*/
chunk = 0xe;
while (len) {
unsigned int clen = min(len, chunk);
cmd.cmd = SMU_CMD_MISC_ee_COMMAND;
cmd.data_len = 7;
cmd.data_buf = params;
cmd.reply_len = chunk;
cmd.reply_buf = dest;
cmd.done = smu_done_complete;
cmd.misc = &comp;
params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC;
params[1] = 0x4;
*((u32 *)&params[2]) = addr;
params[6] = clen;
rc = smu_queue_cmd(&cmd);
if (rc)
return rc;
wait_for_completion(&comp);
if (cmd.status != 0)
return rc;
if (cmd.reply_len != clen) {
printk(KERN_DEBUG "SMU: short read in "
"smu_read_datablock, got: %d, want: %d\n",
cmd.reply_len, clen);
return -EIO;
}
len -= clen;
addr += clen;
dest += clen;
}
return 0;
}
static struct smu_sdbp_header *smu_create_sdb_partition(int id)
{
DECLARE_COMPLETION(comp);
struct smu_simple_cmd cmd;
unsigned int addr, len, tlen;
struct smu_sdbp_header *hdr;
struct property *prop;
/* First query the partition info */
smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2,
smu_done_complete, &comp,
SMU_CMD_PARTITION_LATEST, id);
wait_for_completion(&comp);
/* Partition doesn't exist (or other error) */
if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6)
return NULL;
/* Fetch address and length from reply */
addr = *((u16 *)cmd.buffer);
len = cmd.buffer[3] << 2;
/* Calucluate total length to allocate, including the 17 bytes
* for "sdb-partition-XX" that we append at the end of the buffer
*/
tlen = sizeof(struct property) + len + 18;
prop = kcalloc(tlen, 1, GFP_KERNEL);
if (prop == NULL)
return NULL;
hdr = (struct smu_sdbp_header *)(prop + 1);
prop->name = ((char *)prop) + tlen - 18;
sprintf(prop->name, "sdb-partition-%02x", id);
prop->length = len;
prop->value = (unsigned char *)hdr;
prop->next = NULL;
/* Read the datablock */
if (smu_read_datablock((u8 *)hdr, addr, len)) {
printk(KERN_DEBUG "SMU: datablock read failed while reading "
"partition %02x !\n", id);
goto failure;
}
/* Got it, check a few things and create the property */
if (hdr->id != id) {
printk(KERN_DEBUG "SMU: Reading partition %02x and got "
"%02x !\n", id, hdr->id);
goto failure;
}
if (prom_add_property(smu->of_node, prop)) {
printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x "
"property !\n", id);
goto failure;
}
return hdr;
failure:
kfree(prop);
return NULL;
}
/* Note: Only allowed to return error code in pointers (using ERR_PTR)
* when interruptible is 1
*/
struct smu_sdbp_header *__smu_get_sdb_partition(int id, unsigned int *size,
int interruptible)
{
char pname[32];
struct smu_sdbp_header *part;
if (!smu)
return NULL;
sprintf(pname, "sdb-partition-%02x", id);
return (struct smu_sdbp_header *)get_property(smu->of_node,
if (interruptible) {
int rc;
rc = down_interruptible(&smu_part_access);
if (rc)
return ERR_PTR(rc);
} else
down(&smu_part_access);
part = (struct smu_sdbp_header *)get_property(smu->of_node,
pname, size);
if (part == NULL) {
part = smu_create_sdb_partition(id);
if (part != NULL && size)
*size = part->len << 2;
}
up(&smu_part_access);
return part;
}
struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size)
{
return __smu_get_sdb_partition(id, size, 0);
}
EXPORT_SYMBOL(smu_get_sdb_partition);
......@@ -930,6 +1070,14 @@ static ssize_t smu_write(struct file *file, const char __user *buf,
else if (hdr.cmdtype == SMU_CMDTYPE_WANTS_EVENTS) {
pp->mode = smu_file_events;
return 0;
} else if (hdr.cmdtype == SMU_CMDTYPE_GET_PARTITION) {
struct smu_sdbp_header *part;
part = __smu_get_sdb_partition(hdr.cmd, NULL, 1);
if (part == NULL)
return -EINVAL;
else if (IS_ERR(part))
return PTR_ERR(part);
return 0;
} else if (hdr.cmdtype != SMU_CMDTYPE_SMU)
return -EINVAL;
else if (pp->mode != smu_file_commands)
......
......@@ -48,6 +48,39 @@ static int property_read_proc(char *page, char **start, off_t off,
* and "@10" to it.
*/
/*
* Add a property to a node
*/
static struct proc_dir_entry *
__proc_device_tree_add_prop(struct proc_dir_entry *de, struct property *pp)
{
struct proc_dir_entry *ent;
/*
* Unfortunately proc_register puts each new entry
* at the beginning of the list. So we rearrange them.
*/
ent = create_proc_read_entry(pp->name,
strncmp(pp->name, "security-", 9)
? S_IRUGO : S_IRUSR, de,
property_read_proc, pp);
if (ent == NULL)
return NULL;
if (!strncmp(pp->name, "security-", 9))
ent->size = 0; /* don't leak number of password chars */
else
ent->size = pp->length;
return ent;
}
void proc_device_tree_add_prop(struct proc_dir_entry *pde, struct property *prop)
{
__proc_device_tree_add_prop(pde, prop);
}
/*
* Process a node, adding entries for its children and its properties.
*/
......@@ -57,11 +90,9 @@ void proc_device_tree_add_node(struct device_node *np,
struct property *pp;
struct proc_dir_entry *ent;
struct device_node *child;
struct proc_dir_entry *list = NULL, **lastp;
const char *p;
set_node_proc_entry(np, de);
lastp = &list;
for (child = NULL; (child = of_get_next_child(np, child));) {
p = strrchr(child->full_name, '/');
if (!p)
......@@ -71,9 +102,6 @@ void proc_device_tree_add_node(struct device_node *np,
ent = proc_mkdir(p, de);
if (ent == 0)
break;
*lastp = ent;
ent->next = NULL;
lastp = &ent->next;
proc_device_tree_add_node(child, ent);
}
of_node_put(child);
......@@ -84,7 +112,7 @@ void proc_device_tree_add_node(struct device_node *np,
* properties are quite unimportant for us though, thus we
* simply "skip" them here, but we do have to check.
*/
for (ent = list; ent != NULL; ent = ent->next)
for (ent = de->subdir; ent != NULL; ent = ent->next)
if (!strcmp(ent->name, pp->name))
break;
if (ent != NULL) {
......@@ -94,25 +122,10 @@ void proc_device_tree_add_node(struct device_node *np,
continue;
}
/*
* Unfortunately proc_register puts each new entry
* at the beginning of the list. So we rearrange them.
*/
ent = create_proc_read_entry(pp->name,
strncmp(pp->name, "security-", 9)
? S_IRUGO : S_IRUSR, de,
property_read_proc, pp);
ent = __proc_device_tree_add_prop(de, pp);
if (ent == 0)
break;
if (!strncmp(pp->name, "security-", 9))
ent->size = 0; /* don't leak number of password chars */
else
ent->size = pp->length;
ent->next = NULL;
*lastp = ent;
lastp = &ent->next;
}
de->subdir = list;
}
/*
......
......@@ -203,7 +203,7 @@ extern int prom_n_addr_cells(struct device_node* np);
extern int prom_n_size_cells(struct device_node* np);
extern int prom_n_intr_cells(struct device_node* np);
extern void prom_get_irq_senses(unsigned char *senses, int off, int max);
extern void prom_add_property(struct device_node* np, struct property* prop);
extern int prom_add_property(struct device_node* np, struct property* prop);
#ifdef CONFIG_PPC32
/*
......
......@@ -20,16 +20,52 @@
/*
* Partition info commands
*
* I do not know what those are for at this point
* These commands are used to retreive the sdb-partition-XX datas from
* the SMU. The lenght is always 2. First byte is the subcommand code
* and second byte is the partition ID.
*
* The reply is 6 bytes:
*
* - 0..1 : partition address
* - 2 : a byte containing the partition ID
* - 3 : length (maybe other bits are rest of header ?)
*
* The data must then be obtained with calls to another command:
* SMU_CMD_MISC_ee_GET_DATABLOCK_REC (described below).
*/
#define SMU_CMD_PARTITION_COMMAND 0x3e
#define SMU_CMD_PARTITION_LATEST 0x01
#define SMU_CMD_PARTITION_BASE 0x02
#define SMU_CMD_PARTITION_UPDATE 0x03
/*
* Fan control
*
* This is a "mux" for fan control commands, first byte is the
* "sub" command.
* This is a "mux" for fan control commands. The command seem to
* act differently based on the number of arguments. With 1 byte
* of argument, this seem to be queries for fans status, setpoint,
* etc..., while with 0xe arguments, we will set the fans speeds.
*
* Queries (1 byte arg):
* ---------------------
*
* arg=0x01: read RPM fans status
* arg=0x02: read RPM fans setpoint
* arg=0x11: read PWM fans status
* arg=0x12: read PWM fans setpoint
*
* the "status" queries return the current speed while the "setpoint" ones
* return the programmed/target speed. It _seems_ that the result is a bit
* mask in the first byte of active/available fans, followed by 6 words (16
* bits) containing the requested speed.
*
* Setpoint (14 bytes arg):
* ------------------------
*
* first arg byte is 0 for RPM fans and 0x10 for PWM. Second arg byte is the
* mask of fans affected by the command. Followed by 6 words containing the
* setpoint value for selected fans in the mask (or 0 if mask value is 0)
*/
#define SMU_CMD_FAN_COMMAND 0x4a
......@@ -156,6 +192,14 @@
#define SMU_CMD_POWER_SHUTDOWN "SHUTDOWN"
#define SMU_CMD_POWER_VOLTAGE_SLEW "VSLEW"
/*
* Read ADC sensors
*
* This command takes one byte of parameter: the sensor ID (or "reg"
* value in the device-tree) and returns a 16 bits value
*/
#define SMU_CMD_READ_ADC 0xd8
/* Misc commands
*
* This command seem to be a grab bag of various things
......@@ -176,6 +220,25 @@
* Misc commands
*
* This command seem to be a grab bag of various things
*
* SMU_CMD_MISC_ee_GET_DATABLOCK_REC is used, among others, to
* transfer blocks of data from the SMU. So far, I've decrypted it's
* usage to retreive partition data. In order to do that, you have to
* break your transfer in "chunks" since that command cannot transfer
* more than a chunk at a time. The chunk size used by OF is 0xe bytes,
* but it seems that the darwin driver will let you do 0x1e bytes if
* your "PMU" version is >= 0x30. You can get the "PMU" version apparently
* either in the last 16 bits of property "smu-version-pmu" or as the 16
* bytes at offset 1 of "smu-version-info"
*
* For each chunk, the command takes 7 bytes of arguments:
* byte 0: subcommand code (0x02)
* byte 1: 0x04 (always, I don't know what it means, maybe the address
* space to use or some other nicety. It's hard coded in OF)
* byte 2..5: SMU address of the chunk (big endian 32 bits)
* byte 6: size to transfer (up to max chunk size)
*
* The data is returned directly
*/
#define SMU_CMD_MISC_ee_COMMAND 0xee
#define SMU_CMD_MISC_ee_GET_DATABLOCK_REC 0x02
......@@ -353,11 +416,15 @@ struct smu_sdbp_header {
__u8 flags;
};
/*
* 32 bits integers are usually encoded with 2x16 bits swapped,
* this demangles them
/*
* demangle 16 and 32 bits integer in some SMU partitions
* (currently, afaik, this concerns only the FVT partition
* (0x12)
*/
#define SMU_U32_MIX(x) ((((x) << 16) & 0xffff0000u) | (((x) >> 16) & 0xffffu))
#define SMU_U16_MIX(x) le16_to_cpu(x);
#define SMU_U32_MIX(x) ((((x) & 0xff00ff00u) >> 8)|(((x) & 0x00ff00ffu) << 8))
/* This is the definition of the SMU sdb-partition-0x12 table (called
* CPU F/V/T operating points in Darwin). The definition for all those
......@@ -367,7 +434,8 @@ struct smu_sdbp_header {
struct smu_sdbp_fvt {
__u32 sysclk; /* Base SysClk frequency in Hz for
* this operating point
* this operating point. Value need to
* be unmixed with SMU_U32_MIX()
*/
__u8 pad;
__u8 maxtemp; /* Max temp. supported by this
......@@ -376,10 +444,73 @@ struct smu_sdbp_fvt {
__u16 volts[3]; /* CPU core voltage for the 3
* PowerTune modes, a mode with
* 0V = not supported.
* 0V = not supported. Value need
* to be unmixed with SMU_U16_MIX()
*/
};
/* This partition contains voltage & current sensor calibration
* informations
*/
#define SMU_SDB_CPUVCP_ID 0x21
struct smu_sdbp_cpuvcp {
__u16 volt_scale; /* u4.12 fixed point */
__s16 volt_offset; /* s4.12 fixed point */
__u16 curr_scale; /* u4.12 fixed point */
__s16 curr_offset; /* s4.12 fixed point */
__s32 power_quads[3]; /* s4.28 fixed point */
};
/* This partition contains CPU thermal diode calibration
*/
#define SMU_SDB_CPUDIODE_ID 0x18
struct smu_sdbp_cpudiode {
__u16 m_value; /* u1.15 fixed point */
__s16 b_value; /* s10.6 fixed point */
};
/* This partition contains Slots power calibration
*/
#define SMU_SDB_SLOTSPOW_ID 0x78
struct smu_sdbp_slotspow {
__u16 pow_scale; /* u4.12 fixed point */
__s16 pow_offset; /* s4.12 fixed point */
};
/* This partition contains machine specific version information about
* the sensor/control layout
*/
#define SMU_SDB_SENSORTREE_ID 0x25
struct smu_sdbp_sensortree {
u8 model_id;
u8 unknown[3];
};
/* This partition contains CPU thermal control PID informations. So far
* only single CPU machines have been seen with an SMU, so we assume this
* carries only informations for those
*/
#define SMU_SDB_CPUPIDDATA_ID 0x17
struct smu_sdbp_cpupiddata {
u8 unknown1;
u8 target_temp_delta;
u8 unknown2;
u8 history_len;
s16 power_adj;
u16 max_power;
s32 gp,gr,gd;
};
/* Other partitions without known structures */
#define SMU_SDB_DEBUG_SWITCHES_ID 0x05
#ifdef __KERNEL__
/*
* This returns the pointer to an SMU "sdb" partition data or NULL
......@@ -423,8 +554,10 @@ struct smu_user_cmd_hdr
__u32 cmdtype;
#define SMU_CMDTYPE_SMU 0 /* SMU command */
#define SMU_CMDTYPE_WANTS_EVENTS 1 /* switch fd to events mode */
#define SMU_CMDTYPE_GET_PARTITION 2 /* retreive an sdb partition */
__u8 cmd; /* SMU command byte */
__u8 pad[3]; /* padding */
__u32 data_len; /* Lenght of data following */
};
......
......@@ -93,7 +93,7 @@ extern int device_is_compatible(struct device_node *device, const char *);
extern int machine_is_compatible(const char *compat);
extern unsigned char *get_property(struct device_node *node, const char *name,
int *lenp);
extern void prom_add_property(struct device_node* np, struct property* prop);
extern int prom_add_property(struct device_node* np, struct property* prop);
extern void prom_get_irq_senses(unsigned char *, int, int);
extern int prom_n_addr_cells(struct device_node* np);
extern int prom_n_size_cells(struct device_node* np);
......
......@@ -213,6 +213,6 @@ extern int prom_n_addr_cells(struct device_node* np);
extern int prom_n_size_cells(struct device_node* np);
extern int prom_n_intr_cells(struct device_node* np);
extern void prom_get_irq_senses(unsigned char *senses, int off, int max);
extern void prom_add_property(struct device_node* np, struct property* prop);
extern int prom_add_property(struct device_node* np, struct property* prop);
#endif /* _PPC64_PROM_H */
......@@ -139,15 +139,12 @@ extern void proc_tty_unregister_driver(struct tty_driver *driver);
/*
* proc_devtree.c
*/
#ifdef CONFIG_PROC_DEVICETREE
struct device_node;
struct property;
extern void proc_device_tree_init(void);
#ifdef CONFIG_PROC_DEVICETREE
extern void proc_device_tree_add_node(struct device_node *, struct proc_dir_entry *);
#else /* !CONFIG_PROC_DEVICETREE */
static inline void proc_device_tree_add_node(struct device_node *np, struct proc_dir_entry *pde)
{
return;
}
extern void proc_device_tree_add_prop(struct proc_dir_entry *pde, struct property *prop);
#endif /* CONFIG_PROC_DEVICETREE */
extern struct proc_dir_entry *proc_symlink(const char *,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment