Re: [PATCH 1/6 v5] Kernel DLPAR Infrastructure

Nathan Fontenot
2009-11-02T16:27:29Z

Benjamin Herrenschmidt wrote:
> On Wed, 2009-10-28 at 15:53 -0500, Nathan Fontenot wrote:
>> This patch provides the kernel DLPAR infrastructure in a new filed named
>> dlpar.c. The functionality provided is for acquiring and releasing a resource
>> from firmware and the parsing of information returned from the
>> ibm,configure-connector rtas call. Additionally this exports the pSeries
>> reconfiguration notifier chain so that it can be invoked when device tree
>> updates are made.
>>
>> Signed-off-by: Nathan Fontenot <nfont at austin.ibm.com>
>> ---
>
> Hi Nathan !
>
> Finally I get to review this stuff :-)
>

Thanks!

>> +#define CFG_CONN_WORK_SIZE 4096
>> +static char workarea[CFG_CONN_WORK_SIZE];
>> +static DEFINE_SPINLOCK(workarea_lock);
>
> So I'm not a huge fan of this workarea static. First a static is in
> effect a global name (as far as System.map etc... are concerned) so it
> would warrant a better name. Then, do we really want that 4K of BSS
> taken even on platforms that don't do dlpar ? Any reason why you don't
> just pop a free page with __get_free_page() inside of
> configure_connector() ?
>

I'm not either, having a static buffer and a lock feels like overkill
for this. I tried kmalloc, but that didn't work. I'll try using
__get_free_page.

>> +struct cc_workarea {
>> + u32 drc_index;
>> + u32 zero;
>> + u32 name_offset;
>> + u32 prop_length;
>> + u32 prop_offset;
>> +};
>> +
>> +static struct property *parse_cc_property(char *workarea)
>> +{
>> + struct property *prop;
>> + struct cc_workarea *ccwa;
>> + char *name;
>> + char *value;
>> +
>> + prop = kzalloc(sizeof(*prop), GFP_KERNEL);
>> + if (!prop)
>> + return NULL;
>> +
>> + ccwa = (struct cc_workarea *)workarea;
>> + name = workarea + ccwa->name_offset;
>> + prop->name = kzalloc(strlen(name) + 1, GFP_KERNEL);
>> + if (!prop->name) {
>> + kfree(prop);
>> + return NULL;
>> + }
>> +
>> + strcpy(prop->name, name);
>> +
>> + prop->length = ccwa->prop_length;
>> + value = workarea + ccwa->prop_offset;
>> + prop->value = kzalloc(prop->length, GFP_KERNEL);
>> + if (!prop->value) {
>> + kfree(prop->name);
>> + kfree(prop);
>> + return NULL;
>> + }
>> +
>> + memcpy(prop->value, value, prop->length);
>> + return prop;
>> +}
>> +
>> +static void free_property(struct property *prop)
>> +{
>> + kfree(prop->name);
>> + kfree(prop->value);
>> + kfree(prop);
>> +}
>> +
>> +static struct device_node *parse_cc_node(char *work_area)
>> +{
>
> const char* maybe ?

sure.

>
>> + struct device_node *dn;
>> + struct cc_workarea *ccwa;
>> + char *name;
>> +
>> + dn = kzalloc(sizeof(*dn), GFP_KERNEL);
>> + if (!dn)
>> + return NULL;
>> +
>> + ccwa = (struct cc_workarea *)work_area;
>> + name = work_area + ccwa->name_offset;
>
> I'm wondering whether work_area should be a struct cc_workarea * in the
> first place with a char data[] at the end, but that would mean probably
> tweaking the offsets... no big deal, up to you.
>

I'll look onto that. Anything that makes this easier to understand is good.

>> + dn->full_name = kzalloc(strlen(name) + 1, GFP_KERNEL);
>> + if (!dn->full_name) {
>> + kfree(dn);
>> + return NULL;
>> + }
>> +
>> + strcpy(dn->full_name, name);
>
> kstrdup ?

yep, should have used kstrdup.

>
> .../...
>
>> +#define NEXT_SIBLING 1
>> +#define NEXT_CHILD 2
>> +#define NEXT_PROPERTY 3
>> +#define PREV_PARENT 4
>> +#define MORE_MEMORY 5
>> +#define CALL_AGAIN -2
>> +#define ERR_CFG_USE -9003
>> +
>> +struct device_node *configure_connector(u32 drc_index)
>> +{
>
> It's a global exported function, I'd rather you call it
> dlpar_configure_connector()
>

ok.

>> + struct device_node *dn;
>> + struct device_node *first_dn = NULL;
>> + struct device_node *last_dn = NULL;
>> + struct property *property;
>> + struct property *last_property = NULL;
>> + struct cc_workarea *ccwa;
>> + int cc_token;
>> + int rc;
>> +
>> + cc_token = rtas_token("ibm,configure-connector");
>> + if (cc_token == RTAS_UNKNOWN_SERVICE)
>> + return NULL;
>> +
>> + spin_lock(&workarea_lock);
>> +
>> + ccwa = (struct cc_workarea *)&workarea[0];
>> + ccwa->drc_index = drc_index;
>> + ccwa->zero = 0;
>
> Popping a free page with gfp (or just kmalloc'ing 4K) would avoid the
> need for the lock too.

yes, see comment at beginning.

>
>> + rc = rtas_call(cc_token, 2, 1, NULL, workarea, NULL);
>> + while (rc) {
>> + switch (rc) {
>> + case NEXT_SIBLING:
>> + dn = parse_cc_node(workarea);
>> + if (!dn)
>> + goto cc_error;
>> +
>> + dn->parent = last_dn->parent;
>> + last_dn->sibling = dn;
>> + last_dn = dn;
>> + break;
>> +
>> + case NEXT_CHILD:
>> + dn = parse_cc_node(workarea);
>> + if (!dn)
>> + goto cc_error;
>> +
>> + if (!first_dn)
>> + first_dn = dn;
>> + else {
>> + dn->parent = last_dn;
>> + if (last_dn)
>> + last_dn->child = dn;
>> + }
>> +
>> + last_dn = dn;
>> + break;
>> +
>> + case NEXT_PROPERTY:
>> + property = parse_cc_property(workarea);
>> + if (!property)
>> + goto cc_error;
>> +
>> + if (!last_dn->properties)
>> + last_dn->properties = property;
>> + else
>> + last_property->next = property;
>> +
>> + last_property = property;
>> + break;
>> +
>> + case PREV_PARENT:
>> + last_dn = last_dn->parent;
>> + break;
>> +
>> + case CALL_AGAIN:
>> + break;
>> +
>> + case MORE_MEMORY:
>> + case ERR_CFG_USE:
>> + default:
>> + printk(KERN_ERR "Unexpected Error (%d) "
>> + "returned from configure-connector\n", rc);
>> + goto cc_error;
>> + }
>> +
>> + rc = rtas_call(cc_token, 2, 1, NULL, workarea, NULL);
>> + }
>> +
>> + spin_unlock(&workarea_lock);
>> + return first_dn;
>> +
>> +cc_error:
>> + spin_unlock(&workarea_lock);
>> +
>> + if (first_dn)
>> + free_cc_nodes(first_dn);
>> +
>> + return NULL;
>> +}
>> +
>> +static struct device_node *derive_parent(const char *path)
>> +{
>> + struct device_node *parent;
>> + char parent_path[128];
>> + int parent_path_len;
>> +
>> + parent_path_len = strrchr(path, '/') - path + 1;
>> + strlcpy(parent_path, path, parent_path_len);
>> +
>> + parent = of_find_node_by_path(parent_path);
>> +
>> + return parent;
>> +}
>
> This ...
>
>> +static int add_one_node(struct device_node *dn)
>> +{
>> + struct proc_dir_entry *ent;
>> + int rc;
>> +
>> + of_node_set_flag(dn, OF_DYNAMIC);
>> + kref_init(&dn->kref);
>> + dn->parent = derive_parent(dn->full_name);
>> +
>> + rc = blocking_notifier_call_chain(&pSeries_reconfig_chain,
>> + PSERIES_RECONFIG_ADD, dn);
>> + if (rc == NOTIFY_BAD) {
>> + printk(KERN_ERR "Failed to add device node %s\n",
>> + dn->full_name);
>> + return -ENOMEM; /* For now, safe to assume kmalloc failure */
>> + }
>> +
>> + of_attach_node(dn);
>> +
>> +#ifdef CONFIG_PROC_DEVICETREE
>> + ent = proc_mkdir(strrchr(dn->full_name, '/') + 1, dn->parent->pde);
>> + if (ent)
>> + proc_device_tree_add_node(dn, ent);
>> +#endif
>> +
>> + of_node_put(dn->parent);
>> + return 0;
>> +}
>
> ... and this ...
>
>> +int add_device_tree_nodes(struct device_node *dn)
>> +{
>> + struct device_node *child = dn->child;
>> + struct device_node *sibling = dn->sibling;
>> + int rc;
>> +
>> + dn->child = NULL;
>> + dn->sibling = NULL;
>> + dn->parent = NULL;
>> +
>> + rc = add_one_node(dn);
>> + if (rc)
>> + return rc;
>> +
>> + if (child) {
>> + rc = add_device_tree_nodes(child);
>> + if (rc)
>> + return rc;
>> + }
>> +
>> + if (sibling)
>> + rc = add_device_tree_nodes(sibling);
>> +
>> + return rc;
>> +}
>
> ... and this ...
>
>> +static int remove_one_node(struct device_node *dn)
>> +{
>> + struct device_node *parent = dn->parent;
>> + struct property *prop = dn->properties;
>> +
>> +#ifdef CONFIG_PROC_DEVICETREE
>> + while (prop) {
>> + remove_proc_entry(prop->name, dn->pde);
>> + prop = prop->next;
>> + }
>> +
>> + if (dn->pde)
>> + remove_proc_entry(dn->pde->name, parent->pde);
>> +#endif
>> +
>> + blocking_notifier_call_chain(&pSeries_reconfig_chain,
>> + PSERIES_RECONFIG_REMOVE, dn);
>> + of_detach_node(dn);
>> + of_node_put(dn); /* Must decrement the refcount */
>> +
>> + return 0;
>> +}
>
> ... and this ...
>
>> +static int _remove_device_tree_nodes(struct device_node *dn)
>> +{
>> + int rc;
>> +
>> + if (dn->child) {
>> + rc = _remove_device_tree_nodes(dn->child);
>> + if (rc)
>> + return rc;
>> + }
>> +
>> + if (dn->sibling) {
>> + rc = _remove_device_tree_nodes(dn->sibling);
>> + if (rc)
>> + return rc;
>> + }
>> +
>> + rc = remove_one_node(dn);
>> + return rc;
>> +}
>
> ... repeat myself ...
>
>> +int remove_device_tree_nodes(struct device_node *dn)
>> +{
>> + int rc;
>> +
>> + if (dn->child) {
>> + rc = _remove_device_tree_nodes(dn->child);
>> + if (rc)
>> + return rc;
>> + }
>> +
>> + rc = remove_one_node(dn);
>> + return rc;
>> +}
>
> ... should probably all go to something like drivers/of/dynamic.c or at
> least for now arch/powerpc/kernel/of_dynamic.c along with everything
> related to dynamically adding and removing nodes. I see that potentially
> useful for more than just DLPAR (though DLPAR is the only user right
> now) and should also all be prefixed with of_*

I agree, there should be at least a powerpc generic implementation of these
routines. The reason I put them here is that I am doing some oddities with
the next, child, and sibling pointers since they point to items not yet in
the device tree.

I saw that Grant Likely is doing updates to all of the of_* stuff right now,
would it be ok to have these routines here, renamed as dlpar_*, and look
to merge them in with Grant's updates when he finishes?

>
>> +#define DR_ENTITY_SENSE 9003
>> +#define DR_ENTITY_PRESENT 1
>> +#define DR_ENTITY_UNUSABLE 2
>> +#define ALLOCATION_STATE 9003
>> +#define ALLOC_UNUSABLE 0
>> +#define ALLOC_USABLE 1
>> +#define ISOLATION_STATE 9001
>> +#define ISOLATE 0
>> +#define UNISOLATE 1
>> +
>> +int acquire_drc(u32 drc_index)
>> +{
>> + int dr_status, rc;
>> +
>> + rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
>> + DR_ENTITY_SENSE, drc_index);
>> + if (rc || dr_status != DR_ENTITY_UNUSABLE)
>> + return -1;
>> +
>> + rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_USABLE);
>> + if (rc)
>> + return rc;
>> +
>> + rc = rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
>> + if (rc) {
>> + rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
>> + return rc;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +int release_drc(u32 drc_index)
>> +{
>> + int dr_status, rc;
>> +
>> + rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
>> + DR_ENTITY_SENSE, drc_index);
>> + if (rc || dr_status != DR_ENTITY_PRESENT)
>> + return -1;
>> +
>> + rc = rtas_set_indicator(ISOLATION_STATE, drc_index, ISOLATE);
>> + if (rc)
>> + return rc;
>> +
>> + rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
>> + if (rc) {
>> + rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
>> + return rc;
>> + }
>> +
>> + return 0;
>> +}
>
> Both above should have a dlpar_* prefix

will do.

>
>> +static int pseries_dlpar_init(void)
>> +{
>> + if (!machine_is(pseries))
>> + return 0;
>> +
>> + return 0;
>> +}
>> +device_initcall(pseries_dlpar_init);
>
> What the point ? :-)

Yeah, its a bit odd looking but later patches actually add code to the init routine
to set up memory probe/release and cpu probe/release handlers.

I'll look to add ifdef's around the initcall for cases where no work is to be done.

-Nathan Fontenot

>
> Cheers
> Ben.
>