qemu-ppc
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-ppc] [PATCHv2 1/5] spapr: Introduce DRC subclasses


From: Michael Roth
Subject: Re: [Qemu-ppc] [PATCHv2 1/5] spapr: Introduce DRC subclasses
Date: Mon, 05 Jun 2017 11:32:07 -0500
User-agent: alot/0.5.1

Quoting David Gibson (2017-06-04 22:31:12)
> Currently we only have a single QOM type for all DRCs, but lots of
> places where we switch behaviour based on the DRC's PAPR defined type.
> This is a poor use of our existing type system.
> 
> So, instead create QOM subclasses for each PAPR defined DRC type.  We
> also introduce intermediate subclasses for physical and logical DRCs,
> a division which will be useful later on.
> 
> Instead of being stored in the DRC object itself, the PAPR type is now
> stored in the class structure.  There are still many places where we
> switch directly on the PAPR type value, but this at least provides the
> basis to start to remove those.
> 
> Signed-off-by: David Gibson <address@hidden>

Reviewed-by: Michael Roth <address@hidden>

> ---
>  hw/ppc/spapr.c             |   5 +-
>  hw/ppc/spapr_drc.c         | 123 
> +++++++++++++++++++++++++++++++++------------
>  hw/ppc/spapr_pci.c         |   3 +-
>  include/hw/ppc/spapr_drc.h |  47 +++++++++++++++--
>  4 files changed, 139 insertions(+), 39 deletions(-)
> 
> Mike, here's an updated version of the patch addressing the problems
> you pointed out.  If I can get an ack, I can merge the series, which
> would be nice.

Series:

Acked-by: Michael Roth <address@hidden>

> 
> diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
> index 5d10366..456f9e7 100644
> --- a/hw/ppc/spapr.c
> +++ b/hw/ppc/spapr.c
> @@ -1911,7 +1911,7 @@ static void 
> spapr_create_lmb_dr_connectors(sPAPRMachineState *spapr)
>          uint64_t addr;
>  
>          addr = i * lmb_size + spapr->hotplug_memory.base;
> -        drc = spapr_dr_connector_new(OBJECT(spapr), 
> SPAPR_DR_CONNECTOR_TYPE_LMB,
> +        drc = spapr_dr_connector_new(OBJECT(spapr), TYPE_SPAPR_DRC_LMB,
>                                       addr/lmb_size);
>          qemu_register_reset(spapr_drc_reset, drc);
>      }
> @@ -2008,8 +2008,7 @@ static void spapr_init_cpus(sPAPRMachineState *spapr)
>  
>          if (mc->has_hotpluggable_cpus) {
>              sPAPRDRConnector *drc =
> -                spapr_dr_connector_new(OBJECT(spapr),
> -                                       SPAPR_DR_CONNECTOR_TYPE_CPU,
> +                spapr_dr_connector_new(OBJECT(spapr), TYPE_SPAPR_DRC_CPU,
>                                         (core_id / smp_threads) * smt);
>  
>              qemu_register_reset(spapr_drc_reset, drc);
> diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
> index a35314e..8575022 100644
> --- a/hw/ppc/spapr_drc.c
> +++ b/hw/ppc/spapr_drc.c
> @@ -70,14 +70,23 @@ static sPAPRDRConnectorTypeShift 
> get_type_shift(sPAPRDRConnectorType type)
>      return shift;
>  }
>  
> +sPAPRDRConnectorType spapr_drc_type(sPAPRDRConnector *drc)
> +{
> +    sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
> +
> +    return 1 << drck->typeshift;
> +}
> +
>  uint32_t spapr_drc_index(sPAPRDRConnector *drc)
>  {
> +    sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
> +
>      /* no set format for a drc index: it only needs to be globally
>       * unique. this is how we encode the DRC type on bare-metal
>       * however, so might as well do that here
>       */
> -    return (get_type_shift(drc->type) << DRC_INDEX_TYPE_SHIFT) |
> -            (drc->id & DRC_INDEX_ID_MASK);
> +    return (drck->typeshift << DRC_INDEX_TYPE_SHIFT)
> +        | (drc->id & DRC_INDEX_ID_MASK);
>  }
>  
>  static uint32_t set_isolation_state(sPAPRDRConnector *drc,
> @@ -107,7 +116,7 @@ static uint32_t set_isolation_state(sPAPRDRConnector *drc,
>       * If the LMB being removed doesn't belong to a DIMM device that is
>       * actually being unplugged, fail the isolation request here.
>       */
> -    if (drc->type == SPAPR_DR_CONNECTOR_TYPE_LMB) {
> +    if (spapr_drc_type(drc) == SPAPR_DR_CONNECTOR_TYPE_LMB) {
>          if ((state == SPAPR_DR_ISOLATION_STATE_ISOLATED) &&
>               !drc->awaiting_release) {
>              return RTAS_OUT_HW_ERROR;
> @@ -177,7 +186,7 @@ static uint32_t set_allocation_state(sPAPRDRConnector 
> *drc,
>          }
>      }
>  
> -    if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI) {
> +    if (spapr_drc_type(drc) != SPAPR_DR_CONNECTOR_TYPE_PCI) {
>          drc->allocation_state = state;
>          if (drc->awaiting_release &&
>              drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_UNUSABLE) {
> @@ -191,11 +200,6 @@ static uint32_t set_allocation_state(sPAPRDRConnector 
> *drc,
>      return RTAS_OUT_SUCCESS;
>  }
>  
> -sPAPRDRConnectorType spapr_drc_type(sPAPRDRConnector *drc)
> -{
> -    return drc->type;
> -}
> -
>  static const char *get_name(sPAPRDRConnector *drc)
>  {
>      return drc->name;
> @@ -217,7 +221,7 @@ static void set_signalled(sPAPRDRConnector *drc)
>  static uint32_t entity_sense(sPAPRDRConnector *drc, sPAPRDREntitySense 
> *state)
>  {
>      if (drc->dev) {
> -        if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI &&
> +        if (spapr_drc_type(drc) != SPAPR_DR_CONNECTOR_TYPE_PCI &&
>              drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_UNUSABLE) {
>              /* for logical DR, we return a state of UNUSABLE
>               * iff the allocation state UNUSABLE.
> @@ -235,7 +239,7 @@ static uint32_t entity_sense(sPAPRDRConnector *drc, 
> sPAPRDREntitySense *state)
>              *state = SPAPR_DR_ENTITY_SENSE_PRESENT;
>          }
>      } else {
> -        if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
> +        if (spapr_drc_type(drc) == SPAPR_DR_CONNECTOR_TYPE_PCI) {
>              /* PCI devices, and only PCI devices, use EMPTY
>               * in cases where we'd otherwise use UNUSABLE
>               */
> @@ -368,7 +372,7 @@ static void attach(sPAPRDRConnector *drc, DeviceState *d, 
> void *fdt,
>          error_setg(errp, "an attached device is still awaiting release");
>          return;
>      }
> -    if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
> +    if (spapr_drc_type(drc) == SPAPR_DR_CONNECTOR_TYPE_PCI) {
>          g_assert(drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_USABLE);
>      }
>      g_assert(fdt || coldplug);
> @@ -380,7 +384,7 @@ static void attach(sPAPRDRConnector *drc, DeviceState *d, 
> void *fdt,
>       * may be accessing the device, we can easily crash the guest, so we
>       * we defer completion of removal in such cases to the reset() hook.
>       */
> -    if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
> +    if (spapr_drc_type(drc) == SPAPR_DR_CONNECTOR_TYPE_PCI) {
>          drc->isolation_state = SPAPR_DR_ISOLATION_STATE_UNISOLATED;
>      }
>      drc->indicator_state = SPAPR_DR_INDICATOR_STATE_ACTIVE;
> @@ -398,10 +402,10 @@ static void attach(sPAPRDRConnector *drc, DeviceState 
> *d, void *fdt,
>       * 'physical' DR resources such as PCI where each device/resource is
>       * signalled individually.
>       */
> -    drc->signalled = (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI)
> +    drc->signalled = (spapr_drc_type(drc) != SPAPR_DR_CONNECTOR_TYPE_PCI)
>                       ? true : coldplug;
>  
> -    if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI) {
> +    if (spapr_drc_type(drc) != SPAPR_DR_CONNECTOR_TYPE_PCI) {
>          drc->awaiting_allocation = true;
>      }
>  
> @@ -441,7 +445,7 @@ static void detach(sPAPRDRConnector *drc, DeviceState *d, 
> Error **errp)
>          return;
>      }
>  
> -    if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI &&
> +    if (spapr_drc_type(drc) != SPAPR_DR_CONNECTOR_TYPE_PCI &&
>          drc->allocation_state != SPAPR_DR_ALLOCATION_STATE_UNUSABLE) {
>          trace_spapr_drc_awaiting_unusable(spapr_drc_index(drc));
>          drc->awaiting_release = true;
> @@ -458,8 +462,8 @@ static void detach(sPAPRDRConnector *drc, DeviceState *d, 
> Error **errp)
>  
>      drc->indicator_state = SPAPR_DR_INDICATOR_STATE_INACTIVE;
>  
> -    /* Calling release callbacks based on drc->type. */
> -    switch (drc->type) {
> +    /* Calling release callbacks based on spapr_drc_type(drc). */
> +    switch (spapr_drc_type(drc)) {
>      case SPAPR_DR_CONNECTOR_TYPE_CPU:
>          spapr_core_release(drc->dev);
>          break;
> @@ -515,7 +519,7 @@ static void reset(DeviceState *d)
>          }
>  
>          /* non-PCI devices may be awaiting a transition to UNUSABLE */
> -        if (drc->type != SPAPR_DR_CONNECTOR_TYPE_PCI &&
> +        if (spapr_drc_type(drc) != SPAPR_DR_CONNECTOR_TYPE_PCI &&
>              drc->awaiting_release) {
>              drck->set_allocation_state(drc, 
> SPAPR_DR_ALLOCATION_STATE_UNUSABLE);
>          }
> @@ -544,7 +548,7 @@ static bool spapr_drc_needed(void *opaque)
>       * If there is dev plugged in, we need to migrate the DRC state when
>       * it is different from cold-plugged state
>       */
> -    switch (drc->type) {
> +    switch (spapr_drc_type(drc)) {
>      case SPAPR_DR_CONNECTOR_TYPE_PCI:
>          rc = !((drc->isolation_state == SPAPR_DR_ISOLATION_STATE_UNISOLATED) 
> &&
>                 (drc->allocation_state == SPAPR_DR_ALLOCATION_STATE_USABLE) &&
> @@ -630,17 +634,12 @@ static void unrealize(DeviceState *d, Error **errp)
>      }
>  }
>  
> -sPAPRDRConnector *spapr_dr_connector_new(Object *owner,
> -                                         sPAPRDRConnectorType type,
> +sPAPRDRConnector *spapr_dr_connector_new(Object *owner, const char *type,
>                                           uint32_t id)
>  {
> -    sPAPRDRConnector *drc =
> -        SPAPR_DR_CONNECTOR(object_new(TYPE_SPAPR_DR_CONNECTOR));
> +    sPAPRDRConnector *drc = SPAPR_DR_CONNECTOR(object_new(type));
>      char *prop_name;
>  
> -    g_assert(type);
> -
> -    drc->type = type;
>      drc->id = id;
>      drc->owner = owner;
>      prop_name = g_strdup_printf("dr-connector[%"PRIu32"]",
> @@ -670,7 +669,7 @@ sPAPRDRConnector *spapr_dr_connector_new(Object *owner,
>       * DRC names as documented by PAPR+ v2.7, 13.5.2.4
>       * location codes as documented by PAPR+ v2.7, 12.3.1.5
>       */
> -    switch (drc->type) {
> +    switch (spapr_drc_type(drc)) {
>      case SPAPR_DR_CONNECTOR_TYPE_CPU:
>          drc->name = g_strdup_printf("CPU %d", id);
>          break;
> @@ -689,7 +688,7 @@ sPAPRDRConnector *spapr_dr_connector_new(Object *owner,
>      }
>  
>      /* PCI slot always start in a USABLE state, and stay there */
> -    if (drc->type == SPAPR_DR_CONNECTOR_TYPE_PCI) {
> +    if (spapr_drc_type(drc) == SPAPR_DR_CONNECTOR_TYPE_PCI) {
>          drc->allocation_state = SPAPR_DR_ALLOCATION_STATE_USABLE;
>      }
>  
> @@ -741,6 +740,27 @@ static void spapr_dr_connector_class_init(ObjectClass 
> *k, void *data)
>      dk->user_creatable = false;
>  }
>  
> +static void spapr_drc_cpu_class_init(ObjectClass *k, void *data)
> +{
> +    sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_CLASS(k);
> +
> +    drck->typeshift = SPAPR_DR_CONNECTOR_TYPE_SHIFT_CPU;
> +}
> +
> +static void spapr_drc_pci_class_init(ObjectClass *k, void *data)
> +{
> +    sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_CLASS(k);
> +
> +    drck->typeshift = SPAPR_DR_CONNECTOR_TYPE_SHIFT_PCI;
> +}
> +
> +static void spapr_drc_lmb_class_init(ObjectClass *k, void *data)
> +{
> +    sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_CLASS(k);
> +
> +    drck->typeshift = SPAPR_DR_CONNECTOR_TYPE_SHIFT_LMB;
> +}
> +
>  static const TypeInfo spapr_dr_connector_info = {
>      .name          = TYPE_SPAPR_DR_CONNECTOR,
>      .parent        = TYPE_DEVICE,
> @@ -748,6 +768,42 @@ static const TypeInfo spapr_dr_connector_info = {
>      .instance_init = spapr_dr_connector_instance_init,
>      .class_size    = sizeof(sPAPRDRConnectorClass),
>      .class_init    = spapr_dr_connector_class_init,
> +    .abstract      = true,
> +};
> +
> +static const TypeInfo spapr_drc_physical_info = {
> +    .name          = TYPE_SPAPR_DRC_PHYSICAL,
> +    .parent        = TYPE_SPAPR_DR_CONNECTOR,
> +    .instance_size = sizeof(sPAPRDRConnector),
> +    .abstract      = true,
> +};
> +
> +static const TypeInfo spapr_drc_logical_info = {
> +    .name          = TYPE_SPAPR_DRC_LOGICAL,
> +    .parent        = TYPE_SPAPR_DR_CONNECTOR,
> +    .instance_size = sizeof(sPAPRDRConnector),
> +    .abstract      = true,
> +};
> +
> +static const TypeInfo spapr_drc_cpu_info = {
> +    .name          = TYPE_SPAPR_DRC_CPU,
> +    .parent        = TYPE_SPAPR_DRC_LOGICAL,
> +    .instance_size = sizeof(sPAPRDRConnector),
> +    .class_init    = spapr_drc_cpu_class_init,
> +};
> +
> +static const TypeInfo spapr_drc_pci_info = {
> +    .name          = TYPE_SPAPR_DRC_PCI,
> +    .parent        = TYPE_SPAPR_DRC_PHYSICAL,
> +    .instance_size = sizeof(sPAPRDRConnector),
> +    .class_init    = spapr_drc_pci_class_init,
> +};
> +
> +static const TypeInfo spapr_drc_lmb_info = {
> +    .name          = TYPE_SPAPR_DRC_LMB,
> +    .parent        = TYPE_SPAPR_DRC_LOGICAL,
> +    .instance_size = sizeof(sPAPRDRConnector),
> +    .class_init    = spapr_drc_lmb_class_init,
>  };
>  
>  /* helper functions for external users */
> @@ -858,7 +914,7 @@ int spapr_drc_populate_dt(void *fdt, int fdt_offset, 
> Object *owner,
>              continue;
>          }
>  
> -        if ((drc->type & drc_type_mask) == 0) {
> +        if ((spapr_drc_type(drc) & drc_type_mask) == 0) {
>              continue;
>          }
>  
> @@ -878,7 +934,7 @@ int spapr_drc_populate_dt(void *fdt, int fdt_offset, 
> Object *owner,
>  
>          /* ibm,drc-types */
>          drc_types = g_string_append(drc_types,
> -                                    spapr_drc_get_type_str(drc->type));
> +                                    
> spapr_drc_get_type_str(spapr_drc_type(drc)));
>          drc_types = g_string_insert_len(drc_types, -1, "\0", 1);
>      }
>  
> @@ -1210,6 +1266,11 @@ out:
>  static void spapr_drc_register_types(void)
>  {
>      type_register_static(&spapr_dr_connector_info);
> +    type_register_static(&spapr_drc_physical_info);
> +    type_register_static(&spapr_drc_logical_info);
> +    type_register_static(&spapr_drc_cpu_info);
> +    type_register_static(&spapr_drc_pci_info);
> +    type_register_static(&spapr_drc_lmb_info);
>  
>      spapr_rtas_register(RTAS_SET_INDICATOR, "set-indicator",
>                          rtas_set_indicator);
> diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c
> index 7a208a7..50d673b 100644
> --- a/hw/ppc/spapr_pci.c
> +++ b/hw/ppc/spapr_pci.c
> @@ -1761,8 +1761,7 @@ static void spapr_phb_realize(DeviceState *dev, Error 
> **errp)
>      /* allocate connectors for child PCI devices */
>      if (sphb->dr_enabled) {
>          for (i = 0; i < PCI_SLOT_MAX * 8; i++) {
> -            spapr_dr_connector_new(OBJECT(phb),
> -                                   SPAPR_DR_CONNECTOR_TYPE_PCI,
> +            spapr_dr_connector_new(OBJECT(phb), TYPE_SPAPR_DRC_PCI,
>                                     (sphb->index << 16) | i);
>          }
>      }
> diff --git a/include/hw/ppc/spapr_drc.h b/include/hw/ppc/spapr_drc.h
> index 10e7c24..969c16d 100644
> --- a/include/hw/ppc/spapr_drc.h
> +++ b/include/hw/ppc/spapr_drc.h
> @@ -26,6 +26,48 @@
>  #define SPAPR_DR_CONNECTOR(obj) OBJECT_CHECK(sPAPRDRConnector, (obj), \
>                                               TYPE_SPAPR_DR_CONNECTOR)
>  
> +#define TYPE_SPAPR_DRC_PHYSICAL "spapr-drc-physical"
> +#define SPAPR_DRC_PHYSICAL_GET_CLASS(obj) \
> +        OBJECT_GET_CLASS(sPAPRDRConnectorClass, obj, TYPE_SPAPR_DRC_PHYSICAL)
> +#define SPAPR_DRC_PHYSICAL_CLASS(klass) \
> +        OBJECT_CLASS_CHECK(sPAPRDRConnectorClass, klass, \
> +                           TYPE_SPAPR_DRC_PHYSICAL)
> +#define SPAPR_DRC_PHYSICAL(obj) OBJECT_CHECK(sPAPRDRConnector, (obj), \
> +                                             TYPE_SPAPR_DRC_PHYSICAL)
> +
> +#define TYPE_SPAPR_DRC_LOGICAL "spapr-drc-logical"
> +#define SPAPR_DRC_LOGICAL_GET_CLASS(obj) \
> +        OBJECT_GET_CLASS(sPAPRDRConnectorClass, obj, TYPE_SPAPR_DRC_LOGICAL)
> +#define SPAPR_DRC_LOGICAL_CLASS(klass) \
> +        OBJECT_CLASS_CHECK(sPAPRDRConnectorClass, klass, \
> +                           TYPE_SPAPR_DRC_LOGICAL)
> +#define SPAPR_DRC_LOGICAL(obj) OBJECT_CHECK(sPAPRDRConnector, (obj), \
> +                                             TYPE_SPAPR_DRC_LOGICAL)
> +
> +#define TYPE_SPAPR_DRC_CPU "spapr-drc-cpu"
> +#define SPAPR_DRC_CPU_GET_CLASS(obj) \
> +        OBJECT_GET_CLASS(sPAPRDRConnectorClass, obj, TYPE_SPAPR_DRC_CPU)
> +#define SPAPR_DRC_CPU_CLASS(klass) \
> +        OBJECT_CLASS_CHECK(sPAPRDRConnectorClass, klass, TYPE_SPAPR_DRC_CPU)
> +#define SPAPR_DRC_CPU(obj) OBJECT_CHECK(sPAPRDRConnector, (obj), \
> +                                        TYPE_SPAPR_DRC_CPU)
> +
> +#define TYPE_SPAPR_DRC_PCI "spapr-drc-pci"
> +#define SPAPR_DRC_PCI_GET_CLASS(obj) \
> +        OBJECT_GET_CLASS(sPAPRDRConnectorClass, obj, TYPE_SPAPR_DRC_PCI)
> +#define SPAPR_DRC_PCI_CLASS(klass) \
> +        OBJECT_CLASS_CHECK(sPAPRDRConnectorClass, klass, TYPE_SPAPR_DRC_PCI)
> +#define SPAPR_DRC_PCI(obj) OBJECT_CHECK(sPAPRDRConnector, (obj), \
> +                                        TYPE_SPAPR_DRC_PCI)
> +
> +#define TYPE_SPAPR_DRC_LMB "spapr-drc-lmb"
> +#define SPAPR_DRC_LMB_GET_CLASS(obj) \
> +        OBJECT_GET_CLASS(sPAPRDRConnectorClass, obj, TYPE_SPAPR_DRC_LMB)
> +#define SPAPR_DRC_LMB_CLASS(klass) \
> +        OBJECT_CLASS_CHECK(sPAPRDRConnectorClass, klass, TYPE_SPAPR_DRC_LMB)
> +#define SPAPR_DRC_LMB(obj) OBJECT_CHECK(sPAPRDRConnector, (obj), \
> +                                        TYPE_SPAPR_DRC_LMB)
> +
>  /*
>   * Various hotplug types managed by sPAPRDRConnector
>   *
> @@ -134,7 +176,6 @@ typedef struct sPAPRDRConnector {
>      /*< private >*/
>      DeviceState parent;
>  
> -    sPAPRDRConnectorType type;
>      uint32_t id;
>      Object *owner;
>      const char *name;
> @@ -163,6 +204,7 @@ typedef struct sPAPRDRConnectorClass {
>      DeviceClass parent;
>  
>      /*< public >*/
> +    sPAPRDRConnectorTypeShift typeshift;
>  
>      /* accessors for guest-visible (generally via RTAS) DR state */
>      uint32_t (*set_isolation_state)(sPAPRDRConnector *drc,
> @@ -186,8 +228,7 @@ typedef struct sPAPRDRConnectorClass {
>  uint32_t spapr_drc_index(sPAPRDRConnector *drc);
>  sPAPRDRConnectorType spapr_drc_type(sPAPRDRConnector *drc);
>  
> -sPAPRDRConnector *spapr_dr_connector_new(Object *owner,
> -                                         sPAPRDRConnectorType type,
> +sPAPRDRConnector *spapr_dr_connector_new(Object *owner, const char *type,
>                                           uint32_t id);
>  sPAPRDRConnector *spapr_dr_connector_by_index(uint32_t index);
>  sPAPRDRConnector *spapr_dr_connector_by_id(sPAPRDRConnectorType type,
> -- 
> 2.9.4
> 
> 
> 
> -- 
> David Gibson                    | I'll have my music baroque, and my code
> david AT gibson.dropbear.id.au  | minimalist, thank you.  NOT _the_ _other_
>                                 | _way_ _around_!
> http://www.ozlabs.org/~dgibson




reply via email to

[Prev in Thread] Current Thread [Next in Thread]