diff --git a/sys/powerpc/include/rtas.h b/sys/powerpc/include/rtas.h --- a/sys/powerpc/include/rtas.h +++ b/sys/powerpc/include/rtas.h @@ -41,9 +41,11 @@ * status codes from the bottom of this file. */ +#ifdef _KERNEL int rtas_exists(void); int rtas_call_method(cell_t token, int nargs, int nreturns, ...); cell_t rtas_token_lookup(const char *method); +#endif /* RTAS Status Codes: see CHRP or PAPR specification */ #define RTAS_OK 0 @@ -56,4 +58,268 @@ #define RTAS_ISOLATION_ERROR -9000 #define RTAS_VENDOR_ERROR_BEGIN -9004 +/* + * RTAS event log. + */ + +#define RTAS_ES_BUFSIZE 2048 /* max size of one event log entry */ + +/* Event class */ +#define RTAS_EV_CLASS_INTERNAL 0 /* internal-errors */ +#define RTAS_EV_CLASS_EPOW 1 /* epow-events */ +#define RTAS_EV_CLASS_RSVD1 2 /* reserved */ +#define RTAS_EV_CLASS_HOTPLUG 3 /* hot-plug-events */ +#define RTAS_EV_CLASS_IO 4 /* ibm,io-events */ +#define RTAS_EV_CLASS_MASK(x) (1u << (31 - (x))) + +/* RTAS V6 event log format from "Linux on Power Architecture Reference". */ + +/* re_severity */ +#define RTAS_V6_SEV_NO_ERROR 0 +#define RTAS_V6_SEV_EVENT 1 +#define RTAS_V6_SEV_WARNING 2 +#define RTAS_V6_SEV_ERROR_SYNC 3 +#define RTAS_V6_SEV_ERROR 4 +#define RTAS_V6_SEV_FATAL 5 +#define RTAS_V6_SEV_ALREADY_REPORTED 6 + +/* re_disposition */ +#define RTAS_V6_DISP_FULLY_RECOVERED 0 +#define RTAS_V6_DISP_LIMITED_RECOVERY 1 +#define RTAS_V6_DISP_NOT_RECOVERED 2 + +/* re_optpart */ +#define RTAS_V6_OPTPART_PRESENT 1 +#define RTAS_V6_OPTPART_NOT_PRESENT 0 + +/* re_initiator, re_target */ +#define RTAS_V6_SUBSYS_UNKNOWN 0 +#define RTAS_V6_SUBSYS_CPU 1 +#define RTAS_V6_SUBSYS_PCI 2 +#define RTAS_V6_SUBSYS_MEMORY 4 + +/* re_type */ +#define RTAS_V6_TYPE_OTHER 0 +#define RTAS_V6_TYPE_RETRY 1 +#define RTAS_V6_TYPE_TCE_ERR 2 +#define RTAS_V6_TYPE_INTERN_DEV_FAIL 3 +#define RTAS_V6_TYPE_TIMEOUT 4 +#define RTAS_V6_TYPE_DATA_PARITY 5 +#define RTAS_V6_TYPE_ADDR_PARITY 6 +#define RTAS_V6_TYPE_CACHE_PARITY 7 +#define RTAS_V6_TYPE_ADDR_INVALID 8 +#define RTAS_V6_TYPE_ECC_UNCORR 9 +#define RTAS_V6_TYPE_ECC_CORR 10 + /* 11-63: reserved for future use */ +#define RTAS_V6_TYPE_EPOW 64 + /* 65-95: reserved for future use */ + /* 96-159: reserved - do not reuse */ +#define RTAS_V6_TYPE_PRR 160 /* platform resource reassignment */ + /* 161-223: reserved for future use */ +#define RTAS_V6_TYPE_PLATFORM_ERROR 224 +#define RTAS_V6_TYPE_IO_EVENTS 225 +#define RTAS_V6_TYPE_PLATFORM_INFO 226 +#define RTAS_V6_TYPE_RESOURCE_DEALLOC 227 +#define RTAS_V6_TYPE_DUMP_NOTIFY 228 +#define RTAS_V6_TYPE_HOTPLUG 229 + /* 230-255: vendor-specific */ + +struct rtas_event_v6 { +#define RTAS_V6_VERSION 6 + uint8_t re_version; + + /* byte 1 */ +#if _BYTE_ORDER == _LITTLE_ENDIAN + uint8_t re_reserved:2; + uint8_t re_optpart:1; + uint8_t re_disposition:2; + uint8_t re_severity:3; +#else + uint8_t re_severity:3; + uint8_t re_disposition:2; + uint8_t re_optpart:1; + uint8_t re_reserved:2; +#endif + + /* byte 2 */ +#if _BYTE_ORDER == _LITTLE_ENDIAN + uint8_t re_target:4; + uint8_t re_initiator:4; +#else + uint8_t re_initiator:4; + uint8_t re_target:4; +#endif + + uint8_t re_type; + uint32_t re_extlen; /* length of extended data */ + uint8_t re_extdata[]; +} __packed; + +#ifdef _KERNEL +const char *rtas_name_severity(uint8_t); +const char *rtas_name_disposition(uint8_t); +const char *rtas_name_subsystem(uint8_t); +const char *rtas_name_type(uint8_t); +#endif + +/* RTAS General Extended Event Log Format, Version 6 */ +struct rtas_extlog_v6 { + /* byte 0 */ +#if _BYTE_ORDER == _LITTLE_ENDIAN + uint8_t rx_rsvd1:1; + uint8_t rx_big_endian:1; + uint8_t rx_new:1; + uint8_t rx_predictive:1; + uint8_t rx_unrecoverable_bypassed:1; + uint8_t rx_recoverable:1; + uint8_t rx_unrecoverable:1; + uint8_t rx_log_valid:1; +#else + uint8_t rx_log_valid:1; + uint8_t rx_unrecoverable:1; + uint8_t rx_recoverable:1; + uint8_t rx_unrecoverable_bypassed:1; + uint8_t rx_predictive:1; + uint8_t rx_new:1; + uint8_t rx_big_endian:1; + uint8_t rx_rsvd1:1; +#endif + /* byte 1 */ + uint8_t rx_rsvd2; + /* byte 2 */ +#define RTAS_V6_EXT_LOG_FORMAT_PLATFORM 14 +#if _BYTE_ORDER == _LITTLE_ENDIAN + uint8_t rx_log_format:4; + uint8_t rx_rsvd3:3; + uint8_t rx_ppc_format:1; /* always 1 */ +#else + uint8_t rx_ppc_format:1; /* always 1 */ + uint8_t rx_rsvd3:3; + uint8_t rx_log_format:4; +#endif + /* byte 3-11 */ + uint8_t rx_rsvd4; + uint8_t rx_rsvd5[8]; + /* byte 12-15 */ + char rx_vendor[4]; /* NOT nul terminated */ + /* byte 16-2047 */ + uint8_t rx_vendordata[]; +} __packed; + +_Static_assert(sizeof(struct rtas_extlog_v6) == 16, + "struct rtas_extlog_v6 has the wrong size"); + +/* + * Platform Event Log Format, Version 6. This is used when rx_vendor + * is "IBM\0" and rx_log_format is RTAS_V6_EXT_LOG_FORMAT_PLATFORM. + */ + +/* Generic section header */ +struct rtas_extlog_plt_header { + char ph_section_id[2]; + uint16_t ph_len; +} __packed; + +/* Platform Event Log Format, Main-A section */ +struct rtas_extlog_plt_main_a { + struct rtas_extlog_plt_header + pma_header; + uint8_t pma_version; + uint8_t pma_subtype; + uint16_t pma_ccid; + uint32_t pma_date; /* BCD YYYYMMDD */ + uint32_t pma_time; /* BCD HHMMSSmm */ + uint64_t pma_vendor1; +#define PMA_CREATOR_SERVICE_PROCESSOR 'E' +#define PMA_CREATOR_HYPERVISOR 'H' +#define PMA_CREATOR_POWER_CONTROL 'W' +#define PMA_CREATOR_PARTITION_FIRMWARE 'L' + uint8_t pma_creator; + uint16_t pma_rsvd1; + uint8_t pma_sections; + uint32_t pma_rsvd2; + uint64_t pma_vendor2; + uint32_t pma_plid; + uint32_t pma_vendor3; +} __packed; + +_Static_assert(sizeof(struct rtas_extlog_plt_main_a) == 48, + "struct rtas_extlog_plt_main_a has the wrong size"); + +/* Platform Event Log Format, Main-B section */ +struct rtas_extlog_plt_main_b { + struct rtas_extlog_plt_header + pmb_header; + uint8_t pmb_version; + uint8_t pmb_subtype; + uint16_t pmb_ccid; + uint8_t pmb_subsystem; + uint8_t pmb_vendor1; + uint8_t pmb_event_severity; + uint8_t pmb_event_subtype; + uint32_t pmb_vendor2; + uint16_t pmb_rsvd1; + uint16_t pmb_flags; + uint32_t pmb_rsvd2; +} __packed; + +_Static_assert(sizeof(struct rtas_extlog_plt_main_b) == 24, + "struct rtas_extlog_plt_main_b has the wrong size"); + +struct rtas_extlog_plt_v6 { + struct rtas_extlog_plt_main_a rp_main_a; + struct rtas_extlog_plt_main_b rp_main_b; + uint8_t data[]; /* event-specific data */ +} __packed; + +/* Platform Event Log Format, EPOW section */ +struct rtas_extlog_epow_v6 { + struct rtas_extlog_plt_header + ep_header; + uint8_t ep_version; + uint8_t ep_subtype; + uint16_t ep_ccid; +#define EPOW_ACTION_RESET_MESSAGE 0x00 +#define EPOW_ACTION_WARN_COOLING 0x01 +#define EPOW_ACTION_WARN_POWER 0x02 +#define EPOW_ACTION_SYSTEM_SHUTDOWN 0x03 +#define EPOW_ACTION_SYSTEM_HALT 0x04 +#define EPOW_ACTION_MAIN_ENCLOSURE 0x05 +#define EPOW_ACTION_POWER_OFF 0x07 + uint8_t ep_sensor_value; +#define EPOW_MOD_NORMAL_SHUTDOWN 0x01 +#define EPOW_MOD_UTILITY_POWER_LOSS 0x02 +#define EPOW_MOD_CRITICAL_FUNCTION_LOSS 0x03 +#define EPOW_MOD_TEMP_TOO_HIGH 0x04 + uint8_t ep_modifier; +#define EPOW_EXTMOD_SYSTEM_SHUTDOWN 0x00 +#define EPOW_EXTMOD_PARTITION_SHUTDOWN 0x01 + uint8_t ep_ext_modifier; + uint8_t ep_rsvd1; + uint64_t ep_vendor_reason; +} __packed; + +_Static_assert(sizeof(struct rtas_extlog_epow_v6) == 20, + "struct rtas_extlog_epow_v6 has the wrong size"); + +/* + * Find an extended data section with the given length from an event using its + * two-character id. If the section is not found, or the actual length doesn't + * match the expected length, return NULL. + * + * Note: the caller should have already validated that the event's extended + * data doesn't exceed the buffer size. + */ +#define RTAS_V6_SECTION_MAIN_A_ID "PH" +#define RTAS_V6_SECTION_MAIN_A_SIZE (sizeof(struct rtas_extlog_plt_main_a)) +#define RTAS_V6_SECTION_MAIN_B_ID "UH" +#define RTAS_V6_SECTION_MAIN_B_SIZE (sizeof(struct rtas_extlog_plt_main_b)) +#define RTAS_V6_SECTION_EPOW_ID "EP" +#define RTAS_V6_SECTION_EPOW_SIZE (sizeof(struct rtas_extlog_epow_v6)) + +#ifdef _KERNEL +const void *rtas_find_plt_section(const struct rtas_event_v6 *, + const char *, size_t); +#endif + #endif /* _MACHINE_RTAS_H_ */ diff --git a/sys/powerpc/ofw/rtas.c b/sys/powerpc/ofw/rtas.c --- a/sys/powerpc/ofw/rtas.c +++ b/sys/powerpc/ofw/rtas.c @@ -267,3 +267,118 @@ return (token); } + +const char * +rtas_name_severity(uint8_t sev) +{ + switch (sev) { + case RTAS_V6_SEV_NO_ERROR: return ("NO_ERROR"); + case RTAS_V6_SEV_EVENT: return ("EVENT"); + case RTAS_V6_SEV_WARNING: return ("WARNING"); + case RTAS_V6_SEV_ERROR_SYNC: return ("ERROR_SYNC"); + case RTAS_V6_SEV_ERROR: return ("ERROR"); + case RTAS_V6_SEV_FATAL: return ("FATAL"); + case RTAS_V6_SEV_ALREADY_REPORTED: + return ("ALREADY_REPORTED"); + default: return ("UNKNOWN"); + } +} + +const char * +rtas_name_disposition(uint8_t disp) +{ + switch (disp) { + case RTAS_V6_DISP_FULLY_RECOVERED: return ("FULLY_RECOVERED"); + case RTAS_V6_DISP_LIMITED_RECOVERY: return ("LIMITED_RECOVERY"); + case RTAS_V6_DISP_NOT_RECOVERED: return ("NOT_RECOVERED"); + default: return ("UNKNOWN"); + } +} + +const char *rtas_name_subsystem(uint8_t subsys) +{ + switch (subsys) { + case RTAS_V6_SUBSYS_CPU: return ("CPU"); + case RTAS_V6_SUBSYS_PCI: return ("PCI"); + case RTAS_V6_SUBSYS_MEMORY: return ("MEMORY"); + default: return ("UNKNOWN"); + } +} + +const char *rtas_name_type(uint8_t type) +{ + switch (type) { + case RTAS_V6_TYPE_OTHER: return ("OTHER"); + case RTAS_V6_TYPE_RETRY: return ("RETRY"); + case RTAS_V6_TYPE_TCE_ERR: return ("TCE_ERR"); + case RTAS_V6_TYPE_INTERN_DEV_FAIL: return ("INTERN_DEV_FAIL"); + case RTAS_V6_TYPE_TIMEOUT: return ("TIMEOUT"); + case RTAS_V6_TYPE_DATA_PARITY: return ("DATA_PARITY"); + case RTAS_V6_TYPE_ADDR_PARITY: return ("ADDR_PARITY"); + case RTAS_V6_TYPE_CACHE_PARITY: return ("CACHE_PARITY"); + case RTAS_V6_TYPE_ADDR_INVALID: return ("ADDR_INVALID"); + case RTAS_V6_TYPE_ECC_UNCORR: return ("ECC_UNCORR"); + case RTAS_V6_TYPE_ECC_CORR: return ("ECC_CORR"); + case RTAS_V6_TYPE_EPOW: return ("EPOW"); + case RTAS_V6_TYPE_PRR: return ("PRR"); + case RTAS_V6_TYPE_PLATFORM_ERROR: return ("PLATFORM_ERROR"); + case RTAS_V6_TYPE_IO_EVENTS: return ("IO_EVENTS"); + case RTAS_V6_TYPE_PLATFORM_INFO: return ("PLATFORM_INFO"); + case RTAS_V6_TYPE_RESOURCE_DEALLOC: return ("RESOURCE_DEALLOC"); + case RTAS_V6_TYPE_DUMP_NOTIFY: return ("DUMP_NOTIFY"); + case RTAS_V6_TYPE_HOTPLUG: return ("HOTPLUG"); + default: return ("UNKNOWN"); + } +} + +const void * +rtas_find_plt_section(const struct rtas_event_v6 *event, const char *name, + size_t size) +{ + const struct rtas_extlog_v6 *extlog; + const char *bufp; + size_t bufleft; + + if (event->re_version != RTAS_V6_VERSION) + return (NULL); + + if (event->re_optpart != RTAS_V6_OPTPART_PRESENT) + return (NULL); + + if (be32toh(event->re_extlen) < sizeof(struct rtas_extlog_v6)) + return (NULL); + extlog = (const struct rtas_extlog_v6 *)&event->re_extdata[0]; + + if (!extlog->rx_big_endian) + return (NULL); /* We only know how to read BE logs */ + if (!extlog->rx_ppc_format) + return (NULL); + if (memcmp(extlog->rx_vendor, "IBM\0", 4) != 0) + return (NULL); + if (extlog->rx_log_format != RTAS_V6_EXT_LOG_FORMAT_PLATFORM) + return (NULL); + + bufp = &extlog->rx_vendordata[0]; + bufleft = be32toh(event->re_extlen) - sizeof(struct rtas_extlog_v6); + + while (bufleft >= sizeof(struct rtas_extlog_plt_header)) { + const struct rtas_extlog_plt_header *hdr; + uint16_t sectlen; + + hdr = (const struct rtas_extlog_plt_header *)bufp; + sectlen = be16toh(hdr->ph_len); + if (sectlen > bufleft) + return (NULL); + + if (memcmp(hdr->ph_section_id, name, 2) == 0) { + if (sectlen != size) + return (NULL); + return (hdr); + } + + bufleft -= sectlen; + bufp += sectlen; + } + + return (NULL); +} diff --git a/sys/powerpc/pseries/rtas_dev.c b/sys/powerpc/pseries/rtas_dev.c --- a/sys/powerpc/pseries/rtas_dev.c +++ b/sys/powerpc/pseries/rtas_dev.c @@ -37,23 +37,62 @@ #include #include #include +#include + +#include +#include #include +#include #include +#include +#include #include #include "clock_if.h" +//#define RTAS_DEBUG + +#ifdef RTAS_DEBUG +#define RTAS_DEVPRINTF(dev, msg, ...) device_printf(dev, msg, ##__VA_ARGS__) +#define RTAS_DPRINTF(msg, ...) printf(msg, ##__VA_ARGS__) +#else +#define RTAS_DEVPRINTF(dev, msg, ...) +#define RTAS_DPRINTF(msg, ...) +#endif + static int rtasdev_probe(device_t); static int rtasdev_attach(device_t); +static void rtasdev_find_children(device_t); /* clock interface */ static int rtas_gettime(device_t dev, struct timespec *ts); static int rtas_settime(device_t dev, struct timespec *ts); +/* bus interface */ +static const struct ofw_bus_devinfo * + rtasbus_get_devinfo(device_t dev, device_t child); +static struct resource * + rtasbus_alloc_resource(device_t, device_t, int, int, rman_res_t, + rman_res_t, rman_res_t, u_int); +static int rtasbus_print_child(device_t dev, device_t child); +static struct resource_list * + rtasbus_get_resource_list(device_t dev, device_t child); static void rtas_shutdown(void *arg, int howto); +static void rtas_handle_event(const void *buf, size_t buflen); +static bool rtas_handle_epow(const struct rtas_event_v6 *); -static device_method_t rtasdev_methods[] = { +/* + * The root rtas node. + */ + +struct rtas_devinfo { + struct ofw_bus_devinfo rdi_obdinfo; + struct resource_list rdi_resources; + int rdi_base_irq; +}; + +static device_method_t rtasdev_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rtasdev_probe), DEVMETHOD(device_attach, rtasdev_attach), @@ -62,55 +101,262 @@ DEVMETHOD(clock_gettime, rtas_gettime), DEVMETHOD(clock_settime, rtas_settime), - { 0, 0 }, + /* Bus interface */ + DEVMETHOD(bus_add_child, bus_generic_add_child), + DEVMETHOD(bus_child_pnpinfo, ofw_bus_gen_child_pnpinfo), + DEVMETHOD(bus_print_child, rtasbus_print_child), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_get_resource_list, rtasbus_get_resource_list), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_devinfo, rtasbus_get_devinfo), + DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), + DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), + DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), + DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), + DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), + + DEVMETHOD_END +}; + +struct rtas_softc { + struct mtx sc_mtx; + + phandle_t sc_phandle; + + /* tod clock */ + cell_t sc_gtod_token; /* get-time-of-day */ + /* event log */ + cell_t sc_es_token; /* event-scan */ + struct callout sc_es_callout; + struct task sc_es_task; + int sc_es_interval; + void *sc_buffer; + size_t sc_buflen; }; static driver_t rtasdev_driver = { "rtas", rtasdev_methods, - 0 + sizeof(struct rtas_softc), }; DRIVER_MODULE(rtasdev, ofwbus, rtasdev_driver, 0, 0); +/* + * The exception event handler; this attaches to children of /event-sources. + */ + +static int rtas_esrc_probe(device_t); +static int rtas_esrc_attach(device_t); +static void rtas_esrc_ithread(void *); + +static device_method_t rtas_esrc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, rtas_esrc_probe), + DEVMETHOD(device_attach, rtas_esrc_attach), + + DEVMETHOD_END +}; + +struct rtas_esrc_softc { + struct mtx sc_mtx; + cell_t sc_token; + int sc_irq_rid; + struct resource *sc_irq; + void *sc_intr_cookie; + void *sc_buffer; + size_t sc_buflen; +}; + +static driver_t rtas_esrc_driver = { + "rtas_esrc", + rtas_esrc_methods, + sizeof(struct rtas_esrc_softc), +}; + +DRIVER_MODULE(rtas_esrc, rtas, rtas_esrc_driver, 0, 0); + +/* event log interface */ +static void rtas_event_init(device_t, struct rtas_softc *); +static void rtas_event_callout(void *); +static void rtas_event_task(void *, int); + static int rtasdev_probe(device_t dev) { - const char *name = ofw_bus_get_name(dev); + const char *name; - if (strcmp(name, "rtas") != 0) + name = ofw_bus_get_name(dev); + if (name == NULL || strcmp(name, "rtas") != 0) return (ENXIO); + if (!rtas_exists()) return (ENXIO); device_set_desc(dev, "Run-Time Abstraction Services"); + return (0); } static int rtasdev_attach(device_t dev) { - if (rtas_token_lookup("get-time-of-day") != -1) + struct rtas_softc *sc; + + sc = device_get_softc(dev); + + sc->sc_phandle = OF_finddevice("/rtas"); + if (sc->sc_phandle == -1) { + device_printf(dev, "cannot find RTAS device\n"); + return (ENXIO); + } + + mtx_init(&sc->sc_mtx, "rtas", NULL, MTX_DEF); + + callout_init_mtx(&sc->sc_es_callout, &sc->sc_mtx, 0); + TASK_INIT(&sc->sc_es_task, 0, rtas_event_task, dev); + + /* Attach the tod clock */ + sc->sc_gtod_token = rtas_token_lookup("get-time-of-day"); + if (sc->sc_gtod_token != -1) clock_register(dev, 2000); + /* Attach the periodic log scanner */ + rtas_event_init(dev, sc); + + /* Attach event sources */ + rtasdev_find_children(dev); + EVENTHANDLER_REGISTER(shutdown_final, rtas_shutdown, NULL, SHUTDOWN_PRI_LAST); return (0); } +static void +rtasdev_find_children(device_t dev) +{ + struct rtas_devinfo *dinfo; + cell_t irq[2]; + device_t cdev; + phandle_t event_sources, child; + int err; + + event_sources = OF_finddevice("/event-sources"); + if (event_sources == -1) + return; + + powerpc_register_pic(root_pic, OF_xref_from_node(event_sources), + 1 << 24 /* 24-bit XIRR field */, 1 /* Number of IPIs */, FALSE); + + for (child = OF_child(event_sources); child != 0; + child = OF_peer(child)) { + dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_WAITOK | M_ZERO); + + if (ofw_bus_gen_setup_devinfo(&dinfo->rdi_obdinfo, child) != 0) { + free(dinfo, M_DEVBUF); + continue; + } + + /* + * We need to save the underlying IRQ so we can pass it to + * check-exception later. + */ + err = OF_getencprop(child, "interrupts", &irq[0], sizeof(irq)); + if (err == -1) { + device_printf(dev, "<%s>: failed to fetch interrupts\n", + dinfo->rdi_obdinfo.obd_name); + ofw_bus_gen_destroy_devinfo(&dinfo->rdi_obdinfo); + free(dinfo, M_DEVBUF); + continue; + } + dinfo->rdi_base_irq = irq[0]; + + resource_list_init(&dinfo->rdi_resources); + ofw_bus_intr_to_rl(dev, child, &dinfo->rdi_resources, NULL); + + cdev = device_add_child(dev, NULL, DEVICE_UNIT_ANY); + if (cdev == NULL) { + device_printf(dev, "<%s>: device_add_child failed\n", + dinfo->rdi_obdinfo.obd_name); + ofw_bus_gen_destroy_devinfo(&dinfo->rdi_obdinfo); + free(dinfo, M_DEVBUF); + continue; + } + + device_set_ivars(cdev, dinfo); + } + + bus_attach_children(dev); +} + +static struct resource_list * +rtasbus_get_resource_list(device_t dev, device_t child) +{ + struct rtas_devinfo *dinfo; + + dinfo = device_get_ivars(child); + return (&dinfo->rdi_resources); +} + +static struct resource * +rtasbus_alloc_resource(device_t bus, device_t child, int type, int rid, + rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) +{ + struct rtas_devinfo *dinfo; + + RTAS_DEVPRINTF(child, "alloc_resource, type=%d\n", type); + dinfo = device_get_ivars(child); + + if (type != SYS_RES_IRQ) + return (NULL); + + return (resource_list_alloc(&dinfo->rdi_resources, bus, child, type, + rid, start, end, count, flags)); +} + +static const struct ofw_bus_devinfo * +rtasbus_get_devinfo(device_t dev, device_t child) +{ + return (device_get_ivars(child)); +} + +static int +rtasbus_print_child(device_t dev, device_t child) +{ + struct rtas_devinfo *dinfo; + struct resource_list *rl; + int retval = 0; + + dinfo = device_get_ivars(child); + rl = &dinfo->rdi_resources; + + retval += bus_print_child_header(dev, child); + retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); + retval += bus_print_child_footer(dev, child); + + return (retval); +} + static int rtas_gettime(device_t dev, struct timespec *ts) { struct clocktime ct; + struct rtas_softc *sc; cell_t tod[8]; - cell_t token; int error; - token = rtas_token_lookup("get-time-of-day"); - if (token == -1) + sc = device_get_softc(dev); + + if (sc->sc_gtod_token == -1) return (ENXIO); - error = rtas_call_method(token, 0, 8, &tod[0], &tod[1], &tod[2], - &tod[3], &tod[4], &tod[5], &tod[6], &tod[7]); + error = rtas_call_method(sc->sc_gtod_token, 0, 8, &tod[0], &tod[1], + &tod[2], &tod[3], &tod[4], &tod[5], &tod[6], &tod[7]); if (error < 0) return (ENXIO); if (tod[0] != 0) @@ -131,16 +377,18 @@ rtas_settime(device_t dev, struct timespec *ts) { struct clocktime ct; - cell_t token, status; + struct rtas_softc *sc; + cell_t status; int error; - token = rtas_token_lookup("set-time-of-day"); - if (token == -1) + sc = device_get_softc(dev); + + if (sc->sc_gtod_token == -1) return (ENXIO); clock_ts_to_ct(ts, &ct); - error = rtas_call_method(token, 7, 1, ct.year, ct.mon, ct.day, ct.hour, - ct.min, ct.sec, ct.nsec, &status); + error = rtas_call_method(sc->sc_gtod_token, 7, 1, ct.year, ct.mon, + ct.day, ct.hour, ct.min, ct.sec, ct.nsec, &status); if (error < 0) return (ENXIO); if (status != 0) @@ -168,3 +416,305 @@ rtas_call_method(token, 0, 1, &status); } } + +static void +rtas_event_init(device_t dev, struct rtas_softc *sc) +{ + cell_t scan_rate; + int err; + + sc->sc_es_token = rtas_token_lookup("event-scan"); + if (sc->sc_es_token == -1) + return; + + err = OF_getencprop(sc->sc_phandle, "rtas-event-scan-rate", &scan_rate, + sizeof(scan_rate)); + if (err == -1 || scan_rate <= 0) { + device_printf(dev, "cannot fetch rtas-event-scan-rate, " + "event log scan disabled\n"); + return; + } + + sc->sc_buflen = RTAS_ES_BUFSIZE; + sc->sc_buffer = contigmalloc(sc->sc_buflen, M_DEVBUF, 0, + 0, BUS_SPACE_MAXADDR_32BIT, 8, 256*1024*1024); + if (sc->sc_buffer == NULL) { + device_printf(dev, "cannot allocate event log buffer, " + "event log scan disabled\n"); + return; + } + + RTAS_DEVPRINTF(dev, "event-scan every 1/%u sec\n", + (unsigned)scan_rate); + + sc->sc_es_interval = hz / scan_rate; + callout_reset(&sc->sc_es_callout, sc->sc_es_interval, + rtas_event_callout, dev); +} + +static void +rtas_event_callout(void *arg) +{ + device_t dev; + struct rtas_softc *sc; + + dev = arg; + sc = device_get_softc(dev); + + taskqueue_enqueue(taskqueue_thread, &sc->sc_es_task); +} + +static void +rtas_event_task(void *arg, int pending) +{ + device_t dev; + struct rtas_softc *sc; + int ret; + cell_t status, event_mask, critical, bufaddr, buflen; + + dev = arg; + sc = device_get_softc(dev); + + event_mask = -1; + critical = 0; + bufaddr = (cell_t)vtophys(sc->sc_buffer); + buflen = sc->sc_buflen; + + ret = rtas_call_method(sc->sc_es_token, 4, 1, event_mask, critical, + bufaddr, buflen, &status); + if (ret < 0) { + device_printf(dev, "event-scan failed, status=%d\n", + (int)status); + goto done; + } + + rtas_handle_event(sc->sc_buffer, sc->sc_buflen); + +done: + callout_reset(&sc->sc_es_callout, sc->sc_es_interval, + rtas_event_callout, dev); +} + +static int +rtas_esrc_probe(device_t dev) +{ + const char *name; + + RTAS_DEVPRINTF(dev, "probe\n"); + + name = ofw_bus_get_name(dev); + if (strcmp(name, "epow-events") == 0) + device_set_desc(dev, "Environmental and Power Warnings"); + else if (strcmp(name, "hot-plug-events") == 0) + device_set_desc(dev, "Hot Plug Events"); + else + return (ENXIO); + + return (0); +} + +static int +rtas_esrc_attach(device_t dev) +{ + struct rtas_esrc_softc *sc; + int err; + + RTAS_DEVPRINTF(dev, "attach\n"); + + sc = device_get_softc(dev); + + sc->sc_token = rtas_token_lookup("check-exception"); + if (sc->sc_token == -1) { + device_printf(dev, "cannot look up check-exception token\n"); + return (ENXIO); + } + + sc->sc_buflen = RTAS_ES_BUFSIZE; + sc->sc_buffer = contigmalloc(sc->sc_buflen, M_DEVBUF, 0, + 0, BUS_SPACE_MAXADDR_32BIT, 8, 256*1024*1024); + if (sc->sc_buffer == NULL) { + device_printf(dev, "cannot allocate buffer\n"); + return (ENXIO); + } + + sc->sc_irq_rid = 0; + sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_irq == NULL) { + device_printf(dev, "cannot allocate IRQ\n"); + return (ENXIO); + } + + err = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, rtas_esrc_ithread, dev, &sc->sc_intr_cookie); + if (err != 0) { + device_printf(dev, "bus_setup_intr failed: %d\n", err); + return (ENXIO); + } + + return (0); +} + +static void +rtas_esrc_ithread(void *arg) +{ + device_t dev; + struct rtas_esrc_softc *sc; + struct rtas_devinfo *dinfo; + int ret; + cell_t status, voff, addl, mask, critical, bufaddr, buflen; + + dev = arg; + sc = device_get_softc(dev); + dinfo = device_get_ivars(dev); + + RTAS_DEVPRINTF(dev, "interrupt! irq=%d\n", dinfo->rdi_base_irq); + + voff = 0; + addl = dinfo->rdi_base_irq; + mask = RTAS_EV_CLASS_MASK(RTAS_EV_CLASS_EPOW); + critical = 0; + bufaddr = (cell_t)vtophys(sc->sc_buffer); + buflen = sizeof(sc->sc_buflen); + + do { + ret = rtas_call_method(sc->sc_token, 6, 1, voff, addl, mask, + critical, bufaddr, buflen, &status); + if (ret < 0) { + device_printf(dev, "call-exception failed: %d\n", ret); + return; + } + + RTAS_DEVPRINTF(dev, "status=%d\n", (int)status); + if (status == 0) { + cpu_flush_dcache(sc->sc_buffer, sc->sc_buflen); + rtas_handle_event(sc->sc_buffer, sc->sc_buflen); + } + } while (status == 0); +} + +static void +rtas_handle_event(const void *buf, size_t buflen) +{ + const struct rtas_event_v6 *event; + bool handled; + + event = buf; + + if (event->re_version != RTAS_V6_VERSION) + return; + + /* + * Ensure the total size of the event is within our buffer, + * which is guaranteed by the spec. + */ + if (buflen < sizeof(*event) || + (buflen - sizeof(*event)) < be32toh(event->re_extlen)) + return; + + /* See if we have a type-specific handler */ + switch (event->re_type) { + case RTAS_V6_TYPE_EPOW: + handled = rtas_handle_epow(event); + break; + default: + handled = false; + break; + } + + if (handled) + return; + + /* If we don't recognise the event, log it and do nothing */ + printf("RTAS: Event: severity=%s, disposition=%s, initiator=%s, " + "target=%s, type=%s\n", + rtas_name_severity(event->re_severity), + rtas_name_disposition(event->re_disposition), + rtas_name_subsystem(event->re_initiator), + rtas_name_subsystem(event->re_target), + rtas_name_type(event->re_type)); +} + +static bool +rtas_handle_epow(const struct rtas_event_v6 *event) +{ + const struct rtas_extlog_plt_main_b *mainb; + const struct rtas_extlog_epow_v6 *epow; + const char *reason, *type; + + mainb = rtas_find_plt_section(event, RTAS_V6_SECTION_MAIN_B_ID, + RTAS_V6_SECTION_MAIN_B_SIZE); + if (mainb == NULL) + return (false); /* This is not a platform event log */ + + /* 0xA0 - 0xAF: External environment */ + if (mainb->pmb_subsystem < 0xA0 || mainb->pmb_subsystem > 0xAF) + return (false); + + epow = rtas_find_plt_section(event, RTAS_V6_SECTION_EPOW_ID, + RTAS_V6_SECTION_EPOW_SIZE); + if (epow == NULL) + return (false); + + switch (epow->ep_modifier) { + case EPOW_MOD_NORMAL_SHUTDOWN: + reason = "normal shutdown"; + break; + case EPOW_MOD_UTILITY_POWER_LOSS: + reason = "utility power loss"; + break; + case EPOW_MOD_CRITICAL_FUNCTION_LOSS: + reason = "critical function loss"; + break; + case EPOW_MOD_TEMP_TOO_HIGH: + reason = "ambient temperature too high"; + break; + default: + reason = "unknown"; + break; + } + + switch (epow->ep_ext_modifier) { + case EPOW_EXTMOD_SYSTEM_SHUTDOWN: + type = "system-wide"; + break; + case EPOW_EXTMOD_PARTITION_SHUTDOWN: + type = "partition-specific"; + break; + default: + type = "unknown"; + break; + } + + /* + * According to the spec: + * + * * SYSTEM_SHUTDOWN means the system must begin to shut down after + * an OS defined delay, which is 10 minutes by default. + * + * * SYSTEM_HALT means to shut down within 20 seconds. + * + * * POWER_OFF means the system must halt immediately and we are only + * guaranteed 4ms of remaining power. + * + * We don't currently have a good way to indiciate SYSTEM_SHUTDOWN + * to userland, so treat both SHUTDOWN and HALT as an immediate + * shutdown. + */ + + switch (epow->ep_sensor_value) { + case EPOW_ACTION_SYSTEM_SHUTDOWN: + case EPOW_ACTION_SYSTEM_HALT: + printf("RTAS: EPOW %s shutdown request, reason=%s\n", + type, reason); + shutdown_nice(RB_POWEROFF); + return (true); + case EPOW_ACTION_POWER_OFF: + printf("RTAS: EPOW %s emergency halt, reason=%s\n", + type, reason); + kern_reboot(RB_POWEROFF); + return (true); + default: + return (false); + } +}