Commit ae1c1a8f authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'x86_platform_for_v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull x86 platform updates from Borislav Petkov:

 - add a new uv_sysfs driver and expose read-only information from UV
   BIOS (Justin Ernst and Mike Travis)

 - the usual set of small fixes

* tag 'x86_platform_for_v5.11' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86/platform/uv: Update sysfs documentation
  x86/platform/uv: Add deprecated messages to /proc info leaves
  x86/platform/uv: Add sysfs hubless leaves
  x86/platform/uv: Add sysfs leaves to replace those in procfs
  x86/platform/uv: Add kernel interfaces for obtaining system info
  x86/platform/uv: Make uv_pcibus_kset and uv_hubs_kset static
  x86/platform/uv: Fix an error code in uv_hubs_init()
  x86/platform/uv: Update MAINTAINERS for uv_sysfs driver
  x86/platform/uv: Update ABI documentation of /sys/firmware/sgi_uv/
  x86/platform/uv: Add new uv_sysfs platform driver
  x86/platform/uv: Add and export uv_bios_* functions
  x86/platform/uv: Remove existing /sys/firmware/sgi_uv/ interface
parents 0d712978 c9624cb7
What: /sys/firmware/sgi_uv/
Date: August 2008
Contact: Russ Anderson <rja@sgi.com>
Date: September 2020
Contact: Justin Ernst <justin.ernst@hpe.com>
Description:
The /sys/firmware/sgi_uv directory contains information
about the SGI UV platform.
about the UV platform.
Under that directory are a number of files::
Under that directory are a number of read-only attributes::
archtype
hub_type
hubless
partition_id
coherence_id
uv_type
The archtype entry contains the UV architecture type that
is used to select arch-dependent addresses and features.
It can be set via the OEM_ID in the ACPI MADT table or by
UVsystab entry both passed from UV BIOS.
The hub_type entry is used to select the type of hub which is
similar to uv_type but encoded in a binary format. Include
the file uv_hub.h to get the definitions.
The hubless entry basically is present and set only if there
is no hub. In this case the hub_type entry is not present.
The partition_id entry contains the partition id.
SGI UV systems can be partitioned into multiple physical
UV systems can be partitioned into multiple physical
machines, which each partition running a unique copy
of the operating system. Each partition will have a unique
partition id. To display the partition id, use the command::
cat /sys/firmware/sgi_uv/partition_id
partition id.
The coherence_id entry contains the coherence id.
A partitioned SGI UV system can have one or more coherence
domain. The coherence id indicates which coherence domain
this partition is in. To display the coherence id, use the
command::
A partitioned UV system can have one or more coherence
domains. The coherence id indicates which coherence domain
this partition is in.
The uv_type entry contains the hub revision number.
This value can be used to identify the UV system version::
"0.*" = Hubless UV ('*' is subtype)
"3.0" = UV2
"5.0" = UV3
"7.0" = UV4
"7.1" = UV4a
"9.0" = UV5
The /sys/firmware/sgi_uv directory also contains two directories::
hubs/
pcibuses/
The hubs directory contains a number of hub objects, each representing
a UV Hub visible to the BIOS. Each hub object's name is appended by a
unique ordinal value (ex. /sys/firmware/sgi_uv/hubs/hub_5)
Each hub object directory contains a number of read-only attributes::
cnode
location
name
nasid
shared
this_partition
The cnode entry contains the cnode number of the corresponding hub.
If a cnode value is not applicable, the value returned will be -1.
The location entry contains the location string of the corresponding hub.
This value is used to physically identify a hub within a system.
The name entry contains the name of the corresponding hub. This name can
be two variants::
"UVHub x.x" = A 'node' ASIC, connecting a CPU to the interconnect
fabric. The 'x.x' value represents the ASIC revision.
(ex. 'UVHub 5.0')
"NLxRouter" = A 'router ASIC, only connecting other ASICs to
the interconnect fabric. The 'x' value representing
the fabric technology version. (ex. 'NL8Router')
The nasid entry contains the nasid number of the corresponding hub.
If a nasid value is not applicable, the value returned will be -1.
The shared entry contains a boolean value describing whether the
corresponding hub is shared between system partitions.
The this_partition entry contains a boolean value describing whether
the corresponding hub is local to the current partition.
Each hub object directory also contains a number of port objects,
each representing a fabric port on the corresponding hub.
A port object's name is appended by a unique ordinal value
(ex. /sys/firmware/sgi_uv/hubs/hub_5/port_3)
Each port object directory contains a number of read-only attributes::
conn_hub
conn_port
The conn_hub entry contains a value representing the unique
oridinal value of the hub on the other end of the fabric
cable plugged into the port. If the port is disconnected,
the value returned will be -1.
The conn_port entry contains a value representing the unique
oridinal value of the port on the other end of the fabric cable
plugged into the port. If the port is disconnected, the value
returned will be -1.
Ex:
A value of '3' is read from:
/sys/firmware/sgi_uv/hubs/hub_5/port_3/conn_hub
and a value of '6' is read from:
/sys/firmware/sgi_uv/hubs/hub_5/port_3/conn_port
representing that this port is connected to:
/sys/firmware/sgi_uv/hubs/hub_3/port_6
The pcibuses directory contains a number of PCI bus objects.
Each PCI bus object's name is appended by its PCI bus address.
(ex. pcibus_0003:80)
Each pcibus object has a number of possible read-only attributes::
type
location
slot
ppb_addr
iio_stack
The type entry contains a value describing the type of IO at
the corresponding PCI bus address. Known possible values
across all UV versions are::
BASE IO
PCIe IO
PCIe SLOT
NODE IO
Riser
PPB
The location entry contains the location string of the UV Hub
of the CPU physically connected to the corresponding PCI bus.
The slot entry contains the physical slot number of the
corresponding PCI bus. This value is used to physically locate
PCI cards within a system.
The ppb_addr entry contains the PCI address string of the
bridged PCI bus. This entry is only present when the PCI bus
object type is 'PPB'.
cat /sys/firmware/sgi_uv/coherence_id
The iio_stack entry contains a value describing the IIO stack
number that the corresponding PCI bus object is connected to.
......@@ -18454,6 +18454,12 @@ F: include/uapi/linux/uuid.h
F: lib/test_uuid.c
F: lib/uuid.c
UV SYSFS DRIVER
M: Justin Ernst <justin.ernst@hpe.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/uv_sysfs.c
UVESAFB DRIVER
M: Michal Januszewski <spock@gentoo.org>
L: linux-fbdev@vger.kernel.org
......
......@@ -28,6 +28,20 @@ enum uv_bios_cmd {
UV_BIOS_SET_LEGACY_VGA_TARGET
};
#define UV_BIOS_EXTRA 0x10000
#define UV_BIOS_GET_PCI_TOPOLOGY 0x10001
#define UV_BIOS_GET_GEOINFO 0x10003
#define UV_BIOS_EXTRA_OP_MEM_COPYIN 0x1000
#define UV_BIOS_EXTRA_OP_MEM_COPYOUT 0x2000
#define UV_BIOS_EXTRA_OP_MASK 0x0fff
#define UV_BIOS_EXTRA_GET_HEAPSIZE 1
#define UV_BIOS_EXTRA_INSTALL_HEAP 2
#define UV_BIOS_EXTRA_MASTER_NASID 3
#define UV_BIOS_EXTRA_OBJECT_COUNT (10|UV_BIOS_EXTRA_OP_MEM_COPYOUT)
#define UV_BIOS_EXTRA_ENUM_OBJECTS (12|UV_BIOS_EXTRA_OP_MEM_COPYOUT)
#define UV_BIOS_EXTRA_ENUM_PORTS (13|UV_BIOS_EXTRA_OP_MEM_COPYOUT)
/*
* Status values returned from a BIOS call.
*/
......@@ -109,6 +123,32 @@ struct uv_systab {
} entry[1]; /* additional entries follow */
};
extern struct uv_systab *uv_systab;
#define UV_BIOS_MAXSTRING 128
struct uv_bios_hub_info {
unsigned int id;
union {
struct {
unsigned long long this_part:1;
unsigned long long is_shared:1;
unsigned long long is_disabled:1;
} fields;
struct {
unsigned long long flags;
unsigned long long reserved;
} b;
} f;
char name[UV_BIOS_MAXSTRING];
char location[UV_BIOS_MAXSTRING];
unsigned int ports;
};
struct uv_bios_port_info {
unsigned int port;
unsigned int conn_id;
unsigned int conn_port;
};
/* (... end of definitions from UV BIOS ...) */
enum {
......@@ -142,6 +182,15 @@ extern s64 uv_bios_change_memprotect(u64, u64, enum uv_memprotect);
extern s64 uv_bios_reserved_page_pa(u64, u64 *, u64 *, u64 *);
extern int uv_bios_set_legacy_vga_target(bool decode, int domain, int bus);
extern s64 uv_bios_get_master_nasid(u64 sz, u64 *nasid);
extern s64 uv_bios_get_heapsize(u64 nasid, u64 sz, u64 *heap_sz);
extern s64 uv_bios_install_heap(u64 nasid, u64 sz, u64 *heap);
extern s64 uv_bios_obj_count(u64 nasid, u64 sz, u64 *objcnt);
extern s64 uv_bios_enum_objs(u64 nasid, u64 sz, u64 *objbuf);
extern s64 uv_bios_enum_ports(u64 nasid, u64 obj_id, u64 sz, u64 *portbuf);
extern s64 uv_bios_get_geoinfo(u64 nasid, u64 sz, u64 *geo);
extern s64 uv_bios_get_pci_topology(u64 sz, u64 *buf);
extern int uv_bios_init(void);
extern unsigned long get_uv_systab_phys(bool msg);
......@@ -151,6 +200,8 @@ extern long sn_partition_id;
extern long sn_coherency_id;
extern long sn_region_size;
extern long system_serial_number;
extern ssize_t uv_get_archtype(char *buf, int len);
extern int uv_get_hubless_system(void);
extern struct kobject *sgi_uv_kobj; /* /sys/firmware/sgi_uv */
......
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2020 Hewlett Packard Enterprise Development LP. All rights reserved.
*/
#ifndef _ASM_UV_GEO_H
#define _ASM_UV_GEO_H
/* Type declaractions */
/* Size of a geoid_s structure (must be before decl. of geoid_u) */
#define GEOID_SIZE 8
/* Fields common to all substructures */
struct geo_common_s {
unsigned char type; /* What type of h/w is named by this geoid_s */
unsigned char blade;
unsigned char slot; /* slot is IRU */
unsigned char upos;
unsigned char rack;
};
/* Additional fields for particular types of hardware */
struct geo_node_s {
struct geo_common_s common; /* No additional fields needed */
};
struct geo_rtr_s {
struct geo_common_s common; /* No additional fields needed */
};
struct geo_iocntl_s {
struct geo_common_s common; /* No additional fields needed */
};
struct geo_pcicard_s {
struct geo_iocntl_s common;
char bus; /* Bus/widget number */
char slot; /* PCI slot number */
};
/* Subcomponents of a node */
struct geo_cpu_s {
struct geo_node_s node;
unsigned char socket:4, /* Which CPU on the node */
thread:4;
unsigned char core;
};
struct geo_mem_s {
struct geo_node_s node;
char membus; /* The memory bus on the node */
char memslot; /* The memory slot on the bus */
};
union geoid_u {
struct geo_common_s common;
struct geo_node_s node;
struct geo_iocntl_s iocntl;
struct geo_pcicard_s pcicard;
struct geo_rtr_s rtr;
struct geo_cpu_s cpu;
struct geo_mem_s mem;
char padsize[GEOID_SIZE];
};
/* Defined constants */
#define GEO_MAX_LEN 48
#define GEO_TYPE_INVALID 0
#define GEO_TYPE_MODULE 1
#define GEO_TYPE_NODE 2
#define GEO_TYPE_RTR 3
#define GEO_TYPE_IOCNTL 4
#define GEO_TYPE_IOCARD 5
#define GEO_TYPE_CPU 6
#define GEO_TYPE_MEM 7
#define GEO_TYPE_MAX (GEO_TYPE_MEM+1)
static inline int geo_rack(union geoid_u g)
{
return (g.common.type == GEO_TYPE_INVALID) ?
-1 : g.common.rack;
}
static inline int geo_slot(union geoid_u g)
{
return (g.common.type == GEO_TYPE_INVALID) ?
-1 : g.common.upos;
}
static inline int geo_blade(union geoid_u g)
{
return (g.common.type == GEO_TYPE_INVALID) ?
-1 : g.common.blade * 2 + g.common.slot;
}
#endif /* _ASM_UV_GEO_H */
......@@ -502,6 +502,18 @@ enum uv_system_type get_uv_system_type(void)
return uv_system_type;
}
int uv_get_hubless_system(void)
{
return uv_hubless_system;
}
EXPORT_SYMBOL_GPL(uv_get_hubless_system);
ssize_t uv_get_archtype(char *buf, int len)
{
return scnprintf(buf, len, "%s/%s", uv_archtype, oem_table_id);
}
EXPORT_SYMBOL_GPL(uv_get_archtype);
int is_uv_system(void)
{
return uv_system_type != UV_NONE;
......@@ -1603,21 +1615,30 @@ static void check_efi_reboot(void)
reboot_type = BOOT_ACPI;
}
/* Setup user proc fs files */
/*
* User proc fs file handling now deprecated.
* Recommend using /sys/firmware/sgi_uv/... instead.
*/
static int __maybe_unused proc_hubbed_show(struct seq_file *file, void *data)
{
pr_notice_once("%s: using deprecated /proc/sgi_uv/hubbed, use /sys/firmware/sgi_uv/hub_type\n",
current->comm);
seq_printf(file, "0x%x\n", uv_hubbed_system);
return 0;
}
static int __maybe_unused proc_hubless_show(struct seq_file *file, void *data)
{
pr_notice_once("%s: using deprecated /proc/sgi_uv/hubless, use /sys/firmware/sgi_uv/hubless\n",
current->comm);
seq_printf(file, "0x%x\n", uv_hubless_system);
return 0;
}
static int __maybe_unused proc_archtype_show(struct seq_file *file, void *data)
{
pr_notice_once("%s: using deprecated /proc/sgi_uv/archtype, use /sys/firmware/sgi_uv/archtype\n",
current->comm);
seq_printf(file, "%s/%s\n", uv_archtype, oem_table_id);
return 0;
}
......
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_X86_UV) += bios_uv.o uv_irq.o uv_sysfs.o uv_time.o uv_nmi.o
obj-$(CONFIG_X86_UV) += bios_uv.o uv_irq.o uv_time.o uv_nmi.o
......@@ -72,6 +72,7 @@ static s64 uv_bios_call_irqsave(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3,
long sn_partition_id;
EXPORT_SYMBOL_GPL(sn_partition_id);
long sn_coherency_id;
EXPORT_SYMBOL_GPL(sn_coherency_id);
long sn_region_size;
EXPORT_SYMBOL_GPL(sn_region_size);
long system_serial_number;
......@@ -171,6 +172,60 @@ int uv_bios_set_legacy_vga_target(bool decode, int domain, int bus)
(u64)decode, (u64)domain, (u64)bus, 0, 0);
}
extern s64 uv_bios_get_master_nasid(u64 size, u64 *master_nasid)
{
return uv_bios_call(UV_BIOS_EXTRA, 0, UV_BIOS_EXTRA_MASTER_NASID, 0,
size, (u64)master_nasid);
}
EXPORT_SYMBOL_GPL(uv_bios_get_master_nasid);
extern s64 uv_bios_get_heapsize(u64 nasid, u64 size, u64 *heap_size)
{
return uv_bios_call(UV_BIOS_EXTRA, nasid, UV_BIOS_EXTRA_GET_HEAPSIZE,
0, size, (u64)heap_size);
}
EXPORT_SYMBOL_GPL(uv_bios_get_heapsize);
extern s64 uv_bios_install_heap(u64 nasid, u64 heap_size, u64 *bios_heap)
{
return uv_bios_call(UV_BIOS_EXTRA, nasid, UV_BIOS_EXTRA_INSTALL_HEAP,
0, heap_size, (u64)bios_heap);
}
EXPORT_SYMBOL_GPL(uv_bios_install_heap);
extern s64 uv_bios_obj_count(u64 nasid, u64 size, u64 *objcnt)
{
return uv_bios_call(UV_BIOS_EXTRA, nasid, UV_BIOS_EXTRA_OBJECT_COUNT,
0, size, (u64)objcnt);
}
EXPORT_SYMBOL_GPL(uv_bios_obj_count);
extern s64 uv_bios_enum_objs(u64 nasid, u64 size, u64 *objbuf)
{
return uv_bios_call(UV_BIOS_EXTRA, nasid, UV_BIOS_EXTRA_ENUM_OBJECTS,
0, size, (u64)objbuf);
}
EXPORT_SYMBOL_GPL(uv_bios_enum_objs);
extern s64 uv_bios_enum_ports(u64 nasid, u64 obj_id, u64 size, u64 *portbuf)
{
return uv_bios_call(UV_BIOS_EXTRA, nasid, UV_BIOS_EXTRA_ENUM_PORTS,
obj_id, size, (u64)portbuf);
}
EXPORT_SYMBOL_GPL(uv_bios_enum_ports);
extern s64 uv_bios_get_geoinfo(u64 nasid, u64 size, u64 *buf)
{
return uv_bios_call(UV_BIOS_GET_GEOINFO, nasid, (u64)buf, size, 0, 0);
}
EXPORT_SYMBOL_GPL(uv_bios_get_geoinfo);
extern s64 uv_bios_get_pci_topology(u64 size, u64 *buf)
{
return uv_bios_call(UV_BIOS_GET_PCI_TOPOLOGY, (u64)buf, size, 0, 0, 0);
}
EXPORT_SYMBOL_GPL(uv_bios_get_pci_topology);
unsigned long get_uv_systab_phys(bool msg)
{
if ((uv_systab_phys == EFI_INVALID_TABLE_ADDR) ||
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* This file supports the /sys/firmware/sgi_uv interfaces for SGI UV.
*
* Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
* Copyright (c) Russ Anderson
*/
#include <linux/device.h>
#include <asm/uv/bios.h>
#include <asm/uv/uv.h>
struct kobject *sgi_uv_kobj;
static ssize_t partition_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%ld\n", sn_partition_id);
}
static ssize_t coherence_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%ld\n", sn_coherency_id);
}
static struct kobj_attribute partition_id_attr =
__ATTR(partition_id, S_IRUGO, partition_id_show, NULL);
static struct kobj_attribute coherence_id_attr =
__ATTR(coherence_id, S_IRUGO, coherence_id_show, NULL);
static int __init sgi_uv_sysfs_init(void)
{
unsigned long ret;
if (!is_uv_system())
return -ENODEV;
if (!sgi_uv_kobj)
sgi_uv_kobj = kobject_create_and_add("sgi_uv", firmware_kobj);
if (!sgi_uv_kobj) {
printk(KERN_WARNING "kobject_create_and_add sgi_uv failed\n");
return -EINVAL;
}
ret = sysfs_create_file(sgi_uv_kobj, &partition_id_attr.attr);
if (ret) {
printk(KERN_WARNING "sysfs_create_file partition_id failed\n");
return ret;
}
ret = sysfs_create_file(sgi_uv_kobj, &coherence_id_attr.attr);
if (ret) {
printk(KERN_WARNING "sysfs_create_file coherence_id failed\n");
return ret;
}
return 0;
}
device_initcall(sgi_uv_sysfs_init);
......@@ -78,6 +78,17 @@ config HUAWEI_WMI
To compile this driver as a module, choose M here: the module
will be called huawei-wmi.
config UV_SYSFS
tristate "Sysfs structure for UV systems"
depends on X86_UV
depends on SYSFS
help
This driver supports a sysfs tree describing information about
UV systems at /sys/firmware/sgi_uv/.
To compile this driver as a module, choose M here: the module will
be called uv_sysfs.
config INTEL_WMI_SBL_FW_UPDATE
tristate "Intel WMI Slim Bootloader firmware update signaling driver"
depends on ACPI_WMI
......
......@@ -62,6 +62,9 @@ obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o
obj-$(CONFIG_HP_WMI) += hp-wmi.o
obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
# Hewlett Packard Enterprise
obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o
# IBM Thinkpad and Lenovo
obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* This file supports the /sys/firmware/sgi_uv topology tree on HPE UV.
*
* Copyright (c) 2020 Hewlett Packard Enterprise. All Rights Reserved.
* Copyright (c) Justin Ernst
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/kobject.h>
#include <asm/uv/bios.h>
#include <asm/uv/uv.h>
#include <asm/uv/uv_hub.h>
#include <asm/uv/uv_geo.h>
#define INVALID_CNODE -1
struct kobject *sgi_uv_kobj;
static struct kset *uv_pcibus_kset;
static struct kset *uv_hubs_kset;
static struct uv_bios_hub_info *hub_buf;
static struct uv_bios_port_info **port_buf;
static struct uv_hub **uv_hubs;
static struct uv_pci_top_obj **uv_pci_objs;
static int num_pci_lines;
static int num_cnodes;
static int *prev_obj_to_cnode;
static int uv_bios_obj_cnt;
static signed short uv_master_nasid = -1;
static void *uv_biosheap;
static const char *uv_type_string(void)
{
if (is_uv5_hub())
return "9.0";
else if (is_uv4a_hub())
return "7.1";
else if (is_uv4_hub())
return "7.0";
else if (is_uv3_hub())
return "5.0";
else if (is_uv2_hub())
return "3.0";
else if (uv_get_hubless_system())
return "0.1";
else
return "unknown";
}
static int ordinal_to_nasid(int ordinal)
{
if (ordinal < num_cnodes && ordinal >= 0)
return UV_PNODE_TO_NASID(uv_blade_to_pnode(ordinal));
else
return -1;
}
static union geoid_u cnode_to_geoid(int cnode)
{
union geoid_u geoid;
uv_bios_get_geoinfo(ordinal_to_nasid(cnode), (u64)sizeof(union geoid_u), (u64 *)&geoid);
return geoid;
}
static int location_to_bpos(char *location, int *rack, int *slot, int *blade)
{
char type, r, b, h;
int idb, idh;
if (sscanf(location, "%c%03d%c%02d%c%2d%c%d",
&r, rack, &type, slot, &b, &idb, &h, &idh) != 8)
return -1;
*blade = idb * 2 + idh;
return 0;
}
static int cache_obj_to_cnode(struct uv_bios_hub_info *obj)
{
int cnode;
union geoid_u geoid;
int obj_rack, obj_slot, obj_blade;
int rack, slot, blade;
if (!obj->f.fields.this_part && !obj->f.fields.is_shared)
return 0;
if (location_to_bpos(obj->location, &obj_rack, &obj_slot, &obj_blade))
return -1;
for (cnode = 0; cnode < num_cnodes; cnode++) {
geoid = cnode_to_geoid(cnode);
rack = geo_rack(geoid);
slot = geo_slot(geoid);
blade = geo_blade(geoid);
if (obj_rack == rack && obj_slot == slot && obj_blade == blade)
prev_obj_to_cnode[obj->id] = cnode;
}
return 0;
}
static int get_obj_to_cnode(int obj_id)
{
return prev_obj_to_cnode[obj_id];
}
struct uv_hub {
struct kobject kobj;
struct uv_bios_hub_info *hub_info;
struct uv_port **ports;
};
#define to_uv_hub(kobj_ptr) container_of(kobj_ptr, struct uv_hub, kobj)
static ssize_t hub_name_show(struct uv_bios_hub_info *hub_info, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", hub_info->name);
}
static ssize_t hub_location_show(struct uv_bios_hub_info *hub_info, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", hub_info->location);
}
static ssize_t hub_partition_show(struct uv_bios_hub_info *hub_info, char *buf)
{
return sprintf(buf, "%d\n", hub_info->f.fields.this_part);
}
static ssize_t hub_shared_show(struct uv_bios_hub_info *hub_info, char *buf)
{
return sprintf(buf, "%d\n", hub_info->f.fields.is_shared);
}
static ssize_t hub_nasid_show(struct uv_bios_hub_info *hub_info, char *buf)
{
int cnode = get_obj_to_cnode(hub_info->id);
return sprintf(buf, "%d\n", ordinal_to_nasid(cnode));
}
static ssize_t hub_cnode_show(struct uv_bios_hub_info *hub_info, char *buf)
{
return sprintf(buf, "%d\n", get_obj_to_cnode(hub_info->id));
}
struct hub_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct uv_bios_hub_info *hub_info, char *buf);
ssize_t (*store)(struct uv_bios_hub_info *hub_info, const char *buf, size_t sz);
};
static struct hub_sysfs_entry name_attribute =
__ATTR(name, 0444, hub_name_show, NULL);
static struct hub_sysfs_entry location_attribute =
__ATTR(location, 0444, hub_location_show, NULL);
static struct hub_sysfs_entry partition_attribute =
__ATTR(this_partition, 0444, hub_partition_show, NULL);
static struct hub_sysfs_entry shared_attribute =
__ATTR(shared, 0444, hub_shared_show, NULL);
static struct hub_sysfs_entry nasid_attribute =
__ATTR(nasid, 0444, hub_nasid_show, NULL);
static struct hub_sysfs_entry cnode_attribute =
__ATTR(cnode, 0444, hub_cnode_show, NULL);
static struct attribute *uv_hub_attrs[] = {
&name_attribute.attr,
&location_attribute.attr,
&partition_attribute.attr,
&shared_attribute.attr,
&nasid_attribute.attr,
&cnode_attribute.attr,
NULL,
};
static void hub_release(struct kobject *kobj)
{
struct uv_hub *hub = to_uv_hub(kobj);
kfree(hub);
}
static ssize_t hub_type_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct uv_hub *hub = to_uv_hub(kobj);
struct uv_bios_hub_info *bios_hub_info = hub->hub_info;
struct hub_sysfs_entry *entry;
entry = container_of(attr, struct hub_sysfs_entry, attr);
if (!entry->show)
return -EIO;
return entry->show(bios_hub_info, buf);
}
static const struct sysfs_ops hub_sysfs_ops = {
.show = hub_type_show,
};
static struct kobj_type hub_attr_type = {
.release = hub_release,
.sysfs_ops = &hub_sysfs_ops,
.default_attrs = uv_hub_attrs,
};
static int uv_hubs_init(void)
{
s64 biosr;
u64 sz;
int i, ret;
prev_obj_to_cnode = kmalloc_array(uv_bios_obj_cnt, sizeof(*prev_obj_to_cnode),
GFP_KERNEL);
if (!prev_obj_to_cnode)
return -ENOMEM;
for (i = 0; i < uv_bios_obj_cnt; i++)
prev_obj_to_cnode[i] = INVALID_CNODE;
uv_hubs_kset = kset_create_and_add("hubs", NULL, sgi_uv_kobj);
if (!uv_hubs_kset) {
ret = -ENOMEM;
goto err_hubs_kset;
}
sz = uv_bios_obj_cnt * sizeof(*hub_buf);
hub_buf = kzalloc(sz, GFP_KERNEL);
if (!hub_buf) {
ret = -ENOMEM;
goto err_hub_buf;
}
biosr = uv_bios_enum_objs((u64)uv_master_nasid, sz, (u64 *)hub_buf);
if (biosr) {
ret = -EINVAL;
goto err_enum_objs;
}
uv_hubs = kcalloc(uv_bios_obj_cnt, sizeof(*uv_hubs), GFP_KERNEL);
if (!uv_hubs) {
ret = -ENOMEM;
goto err_enum_objs;
}
for (i = 0; i < uv_bios_obj_cnt; i++) {
uv_hubs[i] = kzalloc(sizeof(*uv_hubs[i]), GFP_KERNEL);
if (!uv_hubs[i]) {
i--;
ret = -ENOMEM;
goto err_hubs;
}
uv_hubs[i]->hub_info = &hub_buf[i];
cache_obj_to_cnode(uv_hubs[i]->hub_info);
uv_hubs[i]->kobj.kset = uv_hubs_kset;
ret = kobject_init_and_add(&uv_hubs[i]->kobj, &hub_attr_type,
NULL, "hub_%u", hub_buf[i].id);
if (ret)
goto err_hubs;
kobject_uevent(&uv_hubs[i]->kobj, KOBJ_ADD);
}
return 0;
err_hubs:
for (; i >= 0; i--)
kobject_put(&uv_hubs[i]->kobj);
kfree(uv_hubs);
err_enum_objs:
kfree(hub_buf);
err_hub_buf:
kset_unregister(uv_hubs_kset);
err_hubs_kset:
kfree(prev_obj_to_cnode);
return ret;
}
static void uv_hubs_exit(void)
{
int i;
for (i = 0; i < uv_bios_obj_cnt; i++)
kobject_put(&uv_hubs[i]->kobj);
kfree(uv_hubs);
kfree(hub_buf);
kset_unregister(uv_hubs_kset);
kfree(prev_obj_to_cnode);
}
struct uv_port {
struct kobject kobj;
struct uv_bios_port_info *port_info;
};
#define to_uv_port(kobj_ptr) container_of(kobj_ptr, struct uv_port, kobj)
static ssize_t uv_port_conn_hub_show(struct uv_bios_port_info *port, char *buf)
{
return sprintf(buf, "%d\n", port->conn_id);
}
static ssize_t uv_port_conn_port_show(struct uv_bios_port_info *port, char *buf)
{
return sprintf(buf, "%d\n", port->conn_port);
}
struct uv_port_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct uv_bios_port_info *port_info, char *buf);
ssize_t (*store)(struct uv_bios_port_info *port_info, const char *buf, size_t size);
};
static struct uv_port_sysfs_entry uv_port_conn_hub_attribute =
__ATTR(conn_hub, 0444, uv_port_conn_hub_show, NULL);
static struct uv_port_sysfs_entry uv_port_conn_port_attribute =
__ATTR(conn_port, 0444, uv_port_conn_port_show, NULL);
static struct attribute *uv_port_attrs[] = {
&uv_port_conn_hub_attribute.attr,
&uv_port_conn_port_attribute.attr,
NULL,
};
static void uv_port_release(struct kobject *kobj)
{
struct uv_port *port = to_uv_port(kobj);
kfree(port);
}
static ssize_t uv_port_type_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct uv_port *port = to_uv_port(kobj);
struct uv_bios_port_info *port_info = port->port_info;
struct uv_port_sysfs_entry *entry;
entry = container_of(attr, struct uv_port_sysfs_entry, attr);
if (!entry->show)
return -EIO;
return entry->show(port_info, buf);
}
static const struct sysfs_ops uv_port_sysfs_ops = {
.show = uv_port_type_show,
};
static struct kobj_type uv_port_attr_type = {
.release = uv_port_release,
.sysfs_ops = &uv_port_sysfs_ops,
.default_attrs = uv_port_attrs,
};
static int uv_ports_init(void)
{
s64 biosr;
int j = 0, k = 0, ret, sz;
port_buf = kcalloc(uv_bios_obj_cnt, sizeof(*port_buf), GFP_KERNEL);
if (!port_buf)
return -ENOMEM;
for (j = 0; j < uv_bios_obj_cnt; j++) {
sz = hub_buf[j].ports * sizeof(*port_buf[j]);
port_buf[j] = kzalloc(sz, GFP_KERNEL);
if (!port_buf[j]) {
ret = -ENOMEM;
j--;
goto err_port_info;
}
biosr = uv_bios_enum_ports((u64)uv_master_nasid, (u64)hub_buf[j].id, sz,
(u64 *)port_buf[j]);
if (biosr) {
ret = -EINVAL;
goto err_port_info;
}
}
for (j = 0; j < uv_bios_obj_cnt; j++) {
uv_hubs[j]->ports = kcalloc(hub_buf[j].ports,
sizeof(*uv_hubs[j]->ports), GFP_KERNEL);
if (!uv_hubs[j]->ports) {
ret = -ENOMEM;
j--;
goto err_ports;
}
}
for (j = 0; j < uv_bios_obj_cnt; j++) {
for (k = 0; k < hub_buf[j].ports; k++) {
uv_hubs[j]->ports[k] = kzalloc(sizeof(*uv_hubs[j]->ports[k]), GFP_KERNEL);
if (!uv_hubs[j]->ports[k]) {
ret = -ENOMEM;
k--;
goto err_kobj_ports;
}
uv_hubs[j]->ports[k]->port_info = &port_buf[j][k];
ret = kobject_init_and_add(&uv_hubs[j]->ports[k]->kobj, &uv_port_attr_type,
&uv_hubs[j]->kobj, "port_%d", port_buf[j][k].port);
if (ret)
goto err_kobj_ports;
kobject_uevent(&uv_hubs[j]->ports[k]->kobj, KOBJ_ADD);
}
}
return 0;
err_kobj_ports:
for (; j >= 0; j--) {
for (; k >= 0; k--)
kobject_put(&uv_hubs[j]->ports[k]->kobj);
if (j > 0)
k = hub_buf[j-1].ports - 1;
}
j = uv_bios_obj_cnt - 1;
err_ports:
for (; j >= 0; j--)
kfree(uv_hubs[j]->ports);
j = uv_bios_obj_cnt - 1;
err_port_info:
for (; j >= 0; j--)
kfree(port_buf[j]);
kfree(port_buf);
return ret;
}
static void uv_ports_exit(void)
{
int j, k;
for (j = 0; j < uv_bios_obj_cnt; j++) {
for (k = hub_buf[j].ports - 1; k >= 0; k--)
kobject_put(&uv_hubs[j]->ports[k]->kobj);
}
for (j = 0; j < uv_bios_obj_cnt; j++) {
kfree(uv_hubs[j]->ports);
kfree(port_buf[j]);
}
kfree(port_buf);
}
struct uv_pci_top_obj {
struct kobject kobj;
char *type;
char *location;
int iio_stack;
char *ppb_addr;
int slot;
};
#define to_uv_pci_top_obj(kobj_ptr) container_of(kobj_ptr, struct uv_pci_top_obj, kobj)
static ssize_t uv_pci_type_show(struct uv_pci_top_obj *top_obj, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", top_obj->type);
}
static ssize_t uv_pci_location_show(struct uv_pci_top_obj *top_obj, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", top_obj->location);
}
static ssize_t uv_pci_iio_stack_show(struct uv_pci_top_obj *top_obj, char *buf)
{
return sprintf(buf, "%d\n", top_obj->iio_stack);
}
static ssize_t uv_pci_ppb_addr_show(struct uv_pci_top_obj *top_obj, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", top_obj->ppb_addr);
}
static ssize_t uv_pci_slot_show(struct uv_pci_top_obj *top_obj, char *buf)
{
return sprintf(buf, "%d\n", top_obj->slot);
}
struct uv_pci_top_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct uv_pci_top_obj *top_obj, char *buf);
ssize_t (*store)(struct uv_pci_top_obj *top_obj, const char *buf, size_t size);
};
static struct uv_pci_top_sysfs_entry uv_pci_type_attribute =
__ATTR(type, 0444, uv_pci_type_show, NULL);
static struct uv_pci_top_sysfs_entry uv_pci_location_attribute =
__ATTR(location, 0444, uv_pci_location_show, NULL);
static struct uv_pci_top_sysfs_entry uv_pci_iio_stack_attribute =
__ATTR(iio_stack, 0444, uv_pci_iio_stack_show, NULL);
static struct uv_pci_top_sysfs_entry uv_pci_ppb_addr_attribute =
__ATTR(ppb_addr, 0444, uv_pci_ppb_addr_show, NULL);
static struct uv_pci_top_sysfs_entry uv_pci_slot_attribute =
__ATTR(slot, 0444, uv_pci_slot_show, NULL);
static void uv_pci_top_release(struct kobject *kobj)
{
struct uv_pci_top_obj *top_obj = to_uv_pci_top_obj(kobj);
kfree(top_obj->type);
kfree(top_obj->location);
kfree(top_obj->ppb_addr);
kfree(top_obj);
}
static ssize_t pci_top_type_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct uv_pci_top_obj *top_obj = to_uv_pci_top_obj(kobj);
struct uv_pci_top_sysfs_entry *entry;
entry = container_of(attr, struct uv_pci_top_sysfs_entry, attr);
if (!entry->show)
return -EIO;
return entry->show(top_obj, buf);
}
static const struct sysfs_ops uv_pci_top_sysfs_ops = {
.show = pci_top_type_show,
};
static struct kobj_type uv_pci_top_attr_type = {
.release = uv_pci_top_release,
.sysfs_ops = &uv_pci_top_sysfs_ops,
};
static int init_pci_top_obj(struct uv_pci_top_obj *top_obj, char *line)
{
char *start;
char type[11], location[14], ppb_addr[15];
int str_cnt, ret;
unsigned int tmp_match[2];
// Minimum line length
if (strlen(line) < 36)
return -EINVAL;
//Line must match format "pcibus %4x:%2x" to be valid
str_cnt = sscanf(line, "pcibus %4x:%2x", &tmp_match[0], &tmp_match[1]);
if (str_cnt < 2)
return -EINVAL;
/* Connect pcibus to segment:bus number with '_'
* to concatenate name tokens.
* pcibus 0000:00 ... -> pcibus_0000:00 ...
*/
line[6] = '_';
/* Null terminate after the concatencated name tokens
* to produce kobj name string.
*/
line[14] = '\0';
// Use start to index after name tokens string for remainder of line info.
start = &line[15];
top_obj->iio_stack = -1;
top_obj->slot = -1;
/* r001i01b00h0 BASE IO (IIO Stack 0)
* r001i01b00h1 PCIe IO (IIO Stack 1)
* r001i01b03h1 PCIe SLOT
* r001i01b00h0 NODE IO
* r001i01b00h0 Riser
* (IIO Stack #) may not be present.
*/
if (start[0] == 'r') {
str_cnt = sscanf(start, "%13s %10[^(] %*s %*s %d)",
location, type, &top_obj->iio_stack);
if (str_cnt < 2)
return -EINVAL;
top_obj->type = kstrdup(type, GFP_KERNEL);
if (!top_obj->type)
return -ENOMEM;
top_obj->location = kstrdup(location, GFP_KERNEL);
if (!top_obj->location) {
kfree(top_obj->type);
return -ENOMEM;
}
}
/* PPB at 0000:80:00.00 (slot 3)
* (slot #) may not be present.
*/
else if (start[0] == 'P') {
str_cnt = sscanf(start, "%10s %*s %14s %*s %d)",
type, ppb_addr, &top_obj->slot);
if (str_cnt < 2)
return -EINVAL;
top_obj->type = kstrdup(type, GFP_KERNEL);
if (!top_obj->type)
return -ENOMEM;
top_obj->ppb_addr = kstrdup(ppb_addr, GFP_KERNEL);
if (!top_obj->ppb_addr) {
kfree(top_obj->type);
return -ENOMEM;
}
} else
return -EINVAL;
top_obj->kobj.kset = uv_pcibus_kset;
ret = kobject_init_and_add(&top_obj->kobj, &uv_pci_top_attr_type, NULL, "%s", line);
if (ret)
goto err_add_sysfs;
if (top_obj->type) {
ret = sysfs_create_file(&top_obj->kobj, &uv_pci_type_attribute.attr);
if (ret)
goto err_add_sysfs;
}
if (top_obj->location) {
ret = sysfs_create_file(&top_obj->kobj, &uv_pci_location_attribute.attr);
if (ret)
goto err_add_sysfs;
}
if (top_obj->iio_stack >= 0) {
ret = sysfs_create_file(&top_obj->kobj, &uv_pci_iio_stack_attribute.attr);
if (ret)
goto err_add_sysfs;
}
if (top_obj->ppb_addr) {
ret = sysfs_create_file(&top_obj->kobj, &uv_pci_ppb_addr_attribute.attr);
if (ret)
goto err_add_sysfs;
}
if (top_obj->slot >= 0) {
ret = sysfs_create_file(&top_obj->kobj, &uv_pci_slot_attribute.attr);
if (ret)
goto err_add_sysfs;
}
kobject_uevent(&top_obj->kobj, KOBJ_ADD);
return 0;
err_add_sysfs:
kobject_put(&top_obj->kobj);
return ret;
}
static int pci_topology_init(void)
{
char *pci_top_str, *start, *found, *count;
size_t sz;
s64 biosr;
int l = 0, k = 0;
int len, ret;
uv_pcibus_kset = kset_create_and_add("pcibuses", NULL, sgi_uv_kobj);
if (!uv_pcibus_kset)
return -ENOMEM;
for (sz = PAGE_SIZE; sz < 16 * PAGE_SIZE; sz += PAGE_SIZE) {
pci_top_str = kmalloc(sz, GFP_KERNEL);
if (!pci_top_str) {
ret = -ENOMEM;
goto err_pci_top_str;
}
biosr = uv_bios_get_pci_topology((u64)sz, (u64 *)pci_top_str);
if (biosr == BIOS_STATUS_SUCCESS) {
len = strnlen(pci_top_str, sz);
for (count = pci_top_str; count < pci_top_str + len; count++) {
if (*count == '\n')
l++;
}
num_pci_lines = l;
uv_pci_objs = kcalloc(num_pci_lines,
sizeof(*uv_pci_objs), GFP_KERNEL);
if (!uv_pci_objs) {
kfree(pci_top_str);
ret = -ENOMEM;
goto err_pci_top_str;
}
start = pci_top_str;
while ((found = strsep(&start, "\n")) != NULL) {
uv_pci_objs[k] = kzalloc(sizeof(*uv_pci_objs[k]), GFP_KERNEL);
if (!uv_pci_objs[k]) {
ret = -ENOMEM;
goto err_pci_obj;
}
ret = init_pci_top_obj(uv_pci_objs[k], found);
if (ret)
goto err_pci_obj;
k++;
if (k == num_pci_lines)
break;
}
}
kfree(pci_top_str);
if (biosr == BIOS_STATUS_SUCCESS || biosr == BIOS_STATUS_UNIMPLEMENTED)
break;
}
return 0;
err_pci_obj:
k--;
for (; k >= 0; k--)
kobject_put(&uv_pci_objs[k]->kobj);
kfree(uv_pci_objs);
kfree(pci_top_str);
err_pci_top_str:
kset_unregister(uv_pcibus_kset);
return ret;
}
static void pci_topology_exit(void)
{
int k;
for (k = 0; k < num_pci_lines; k++)
kobject_put(&uv_pci_objs[k]->kobj);
kset_unregister(uv_pcibus_kset);
kfree(uv_pci_objs);
}
static ssize_t partition_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%ld\n", sn_partition_id);
}
static ssize_t coherence_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%ld\n", sn_coherency_id);
}
static ssize_t uv_type_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", uv_type_string());
}
static ssize_t uv_archtype_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return uv_get_archtype(buf, PAGE_SIZE);
}
static ssize_t uv_hub_type_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "0x%x\n", uv_hub_type());
}
static ssize_t uv_hubless_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "0x%x\n", uv_get_hubless_system());
}
static struct kobj_attribute partition_id_attr =
__ATTR(partition_id, 0444, partition_id_show, NULL);
static struct kobj_attribute coherence_id_attr =
__ATTR(coherence_id, 0444, coherence_id_show, NULL);
static struct kobj_attribute uv_type_attr =
__ATTR(uv_type, 0444, uv_type_show, NULL);
static struct kobj_attribute uv_archtype_attr =
__ATTR(archtype, 0444, uv_archtype_show, NULL);
static struct kobj_attribute uv_hub_type_attr =
__ATTR(hub_type, 0444, uv_hub_type_show, NULL);
static struct kobj_attribute uv_hubless_attr =
__ATTR(hubless, 0444, uv_hubless_show, NULL);
static struct attribute *base_attrs[] = {
&partition_id_attr.attr,
&coherence_id_attr.attr,
&uv_type_attr.attr,
&uv_archtype_attr.attr,
&uv_hub_type_attr.attr,
NULL,
};
static struct attribute_group base_attr_group = {
.attrs = base_attrs
};
static int initial_bios_setup(void)
{
u64 v;
s64 biosr;
biosr = uv_bios_get_master_nasid((u64)sizeof(uv_master_nasid), (u64 *)&uv_master_nasid);
if (biosr)
return -EINVAL;
biosr = uv_bios_get_heapsize((u64)uv_master_nasid, (u64)sizeof(u64), &v);
if (biosr)
return -EINVAL;
uv_biosheap = vmalloc(v);
if (!uv_biosheap)
return -ENOMEM;
biosr = uv_bios_install_heap((u64)uv_master_nasid, v, (u64 *)uv_biosheap);
if (biosr) {
vfree(uv_biosheap);
return -EINVAL;
}
biosr = uv_bios_obj_count((u64)uv_master_nasid, sizeof(u64), &v);
if (biosr) {
vfree(uv_biosheap);
return -EINVAL;
}
uv_bios_obj_cnt = (int)v;
return 0;
}
static struct attribute *hubless_base_attrs[] = {
&partition_id_attr.attr,
&uv_type_attr.attr,
&uv_archtype_attr.attr,
&uv_hubless_attr.attr,
NULL,
};
static struct attribute_group hubless_base_attr_group = {
.attrs = hubless_base_attrs
};
static int __init uv_sysfs_hubless_init(void)
{
int ret;
ret = sysfs_create_group(sgi_uv_kobj, &hubless_base_attr_group);
if (ret) {
pr_warn("sysfs_create_group hubless_base_attr_group failed\n");
kobject_put(sgi_uv_kobj);
}
return ret;
}
static int __init uv_sysfs_init(void)
{
int ret = 0;
if (!is_uv_system() && !uv_get_hubless_system())
return -ENODEV;
num_cnodes = uv_num_possible_blades();
if (!sgi_uv_kobj)
sgi_uv_kobj = kobject_create_and_add("sgi_uv", firmware_kobj);
if (!sgi_uv_kobj) {
pr_warn("kobject_create_and_add sgi_uv failed\n");
return -EINVAL;
}
if (uv_get_hubless_system())
return uv_sysfs_hubless_init();
ret = sysfs_create_group(sgi_uv_kobj, &base_attr_group);
if (ret) {
pr_warn("sysfs_create_group base_attr_group failed\n");
goto err_create_group;
}
ret = initial_bios_setup();
if (ret)
goto err_bios_setup;
ret = uv_hubs_init();
if (ret)
goto err_hubs_init;
ret = uv_ports_init();
if (ret)
goto err_ports_init;
ret = pci_topology_init();
if (ret)
goto err_pci_init;
return 0;
err_pci_init:
uv_ports_exit();
err_ports_init:
uv_hubs_exit();
err_hubs_init:
vfree(uv_biosheap);
err_bios_setup:
sysfs_remove_group(sgi_uv_kobj, &base_attr_group);
err_create_group:
kobject_put(sgi_uv_kobj);
return ret;
}
static void __exit uv_sysfs_hubless_exit(void)
{
sysfs_remove_group(sgi_uv_kobj, &hubless_base_attr_group);
kobject_put(sgi_uv_kobj);
}
static void __exit uv_sysfs_exit(void)
{
if (!is_uv_system()) {
if (uv_get_hubless_system())
uv_sysfs_hubless_exit();
return;
}
pci_topology_exit();
uv_ports_exit();
uv_hubs_exit();
vfree(uv_biosheap);
sysfs_remove_group(sgi_uv_kobj, &base_attr_group);
kobject_put(sgi_uv_kobj);
}
#ifndef MODULE
device_initcall(uv_sysfs_init);
#else
module_init(uv_sysfs_init);
#endif
module_exit(uv_sysfs_exit);
MODULE_AUTHOR("Hewlett Packard Enterprise");
MODULE_LICENSE("GPL");
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