[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH 1/3] x86: refactor psr implementation in hypervisor.
Current psr.c is designed for supporting L3 CAT/CDP. It has many limitations to add new feature. Considering to support more PSR features, we need refactor PSR implementation to make it more flexible and fulfill the principle, open for extension but closed for modification. The core of the refactoring is to abstract the common actions and encapsulate them into "struct feat_ops". The detailed steps to add a new feature are described at the head of psr.c. Signed-off-by: Yi Sun <yi.y.sun@xxxxxxxxxxxxxxx> --- xen/arch/x86/domctl.c | 36 +- xen/arch/x86/psr.c | 1121 +++++++++++++++++++++++++++++++++++---------- xen/arch/x86/sysctl.c | 9 +- xen/include/asm-x86/psr.h | 21 +- 4 files changed, 912 insertions(+), 275 deletions(-) diff --git a/xen/arch/x86/domctl.c b/xen/arch/x86/domctl.c index bed70aa..a51ed2c 100644 --- a/xen/arch/x86/domctl.c +++ b/xen/arch/x86/domctl.c @@ -1358,41 +1358,41 @@ long arch_do_domctl( switch ( domctl->u.psr_cat_op.cmd ) { case XEN_DOMCTL_PSR_CAT_OP_SET_L3_CBM: - ret = psr_set_l3_cbm(d, domctl->u.psr_cat_op.target, - domctl->u.psr_cat_op.data, - PSR_CBM_TYPE_L3); + ret = psr_set_val(d, domctl->u.psr_cat_op.target, + domctl->u.psr_cat_op.data, + PSR_MASK_TYPE_L3_CBM); break; case XEN_DOMCTL_PSR_CAT_OP_SET_L3_CODE: - ret = psr_set_l3_cbm(d, domctl->u.psr_cat_op.target, - domctl->u.psr_cat_op.data, - PSR_CBM_TYPE_L3_CODE); + ret = psr_set_val(d, domctl->u.psr_cat_op.target, + domctl->u.psr_cat_op.data, + PSR_MASK_TYPE_L3_CODE); break; case XEN_DOMCTL_PSR_CAT_OP_SET_L3_DATA: - ret = psr_set_l3_cbm(d, domctl->u.psr_cat_op.target, - domctl->u.psr_cat_op.data, - PSR_CBM_TYPE_L3_DATA); + ret = psr_set_val(d, domctl->u.psr_cat_op.target, + domctl->u.psr_cat_op.data, + PSR_MASK_TYPE_L3_DATA); break; case XEN_DOMCTL_PSR_CAT_OP_GET_L3_CBM: - ret = psr_get_l3_cbm(d, domctl->u.psr_cat_op.target, - &domctl->u.psr_cat_op.data, - PSR_CBM_TYPE_L3); + ret = psr_get_val(d, domctl->u.psr_cat_op.target, + &domctl->u.psr_cat_op.data, + PSR_MASK_TYPE_L3_CBM); copyback = 1; break; case XEN_DOMCTL_PSR_CAT_OP_GET_L3_CODE: - ret = psr_get_l3_cbm(d, domctl->u.psr_cat_op.target, - &domctl->u.psr_cat_op.data, - PSR_CBM_TYPE_L3_CODE); + ret = psr_get_val(d, domctl->u.psr_cat_op.target, + &domctl->u.psr_cat_op.data, + PSR_MASK_TYPE_L3_CODE); copyback = 1; break; case XEN_DOMCTL_PSR_CAT_OP_GET_L3_DATA: - ret = psr_get_l3_cbm(d, domctl->u.psr_cat_op.target, - &domctl->u.psr_cat_op.data, - PSR_CBM_TYPE_L3_DATA); + ret = psr_get_val(d, domctl->u.psr_cat_op.target, + &domctl->u.psr_cat_op.data, + PSR_MASK_TYPE_L3_DATA); copyback = 1; break; diff --git a/xen/arch/x86/psr.c b/xen/arch/x86/psr.c index 0b5073c..a77b55a 100644 --- a/xen/arch/x86/psr.c +++ b/xen/arch/x86/psr.c @@ -23,22 +23,116 @@ #define PSR_CAT (1<<1) #define PSR_CDP (1<<2) -struct psr_cat_cbm { - union { - uint64_t cbm; - struct { - uint64_t code; - uint64_t data; - }; - }; +/* + * To add a new PSR feature, please follow below steps: + * 1. Implement feature specific callback functions listed in + * "struct feat_ops"; + * 2. Implement a feature specific "struct feat_ops" object and register + * feature specific callback functions into it; + * 3. Delcare feat_list object for the feature and malloc memory for it + * in internal_cpu_prepare(). Correspondingly, free them in + * free_feature(); + * 4. Add feature initialization codes in internal_cpu_init(). + */ + +struct feat_list; +struct psr_socket_alloc_info; + +/* Every feature enabled should implement such ops and callback functions. */ +struct feat_ops { + /* + * init_feature is used in cpu initialization process to do feature + * specific initialization works. + */ + void (*init_feature)(unsigned int eax, unsigned int ebx, + unsigned int ecx, unsigned int edx, + struct feat_list *pFeat, + struct psr_socket_alloc_info *info); + /* + * get_old_set_new is used in set value process to get all features' + * COS registers values according to original cos id of the domain. + * Then, assemble them into an mask array as feature list order. + * Then, set the new feature value into feature corresponding position + * in the array. This function is used to pair with compare_mask. + */ + int (*get_old_set_new)(uint64_t *mask, + struct feat_list *pFeat, + unsigned int old_cos, + enum mask_type type, + uint64_t m); + /* + * compare_mask is used to in set value process to compare if the + * input mask array can match all the features' COS registers values + * according to input cos id. + */ + int (*compare_mask)(uint64_t *mask, struct feat_list *pFeat, + unsigned int cos, bool_t *found); + /* + * get_cos_max_as_type is used to get the cos_max value of the feature + * according to input mask_type. + */ + unsigned int (*get_cos_max_as_type)(struct feat_list *pFeat, + enum mask_type type); + /* + * exceed_range is used to check if the input cos id exceeds the + * feature's cos_max and if the input mask value is not the default one. + * Even if the associated cos exceeds the cos_max, HW can work as default + * value. That is the reason we need check is mask value is default one. + * If both criteria are fulfilled, that means the input exceeds the + * range. + */ + unsigned int (*exceed_range)(uint64_t *mask, struct feat_list *pFeat, + unsigned int cos); + /* + * write_msr is used to write feature specific MSR registers. + */ + int (*write_msr)(unsigned int cos, uint64_t *mask, + struct feat_list *pFeat); + /* + * get_val is used to get feature specific COS register value. + */ + int (*get_val)(struct feat_list *pFeat, unsigned int cos, + enum mask_type type, uint64_t *val); + /* + * get_feat_info is used to get feature specific HW info. + */ + int (*get_feat_info)(struct feat_list *pFeat, enum mask_type type, + uint32_t *dat0, uint32_t *dat1, + uint32_t *dat2); + /* + * get_max_cos_max is used to get feature's cos_max. + */ + unsigned int (*get_max_cos_max)(struct feat_list *pFeat); +}; + +#define MAX_FEAT_INFO_SIZE 8 +#define MAX_COS_REG_NUM 128 + +struct feat_list { + unsigned int feature; + struct feat_ops ops; + unsigned int feat_info[MAX_FEAT_INFO_SIZE]; + uint64_t cos_reg_val[MAX_COS_REG_NUM]; + struct feat_list *pNext; +}; + +struct psr_ref { unsigned int ref; }; -struct psr_cat_socket_info { - unsigned int cbm_len; - unsigned int cos_max; - struct psr_cat_cbm *cos_to_cbm; - spinlock_t cbm_lock; + +#define PSR_SOCKET_L3_CAT 0 +#define PSR_SOCKET_L3_CDP 1 + +struct psr_socket_alloc_info { + /* + * bit 1~0: [01]->L3 CAT-only, [10]->L3 CDP + */ + unsigned int features; + unsigned int nr_feat; + struct feat_list *pFeat; + struct psr_ref *cos_ref; + spinlock_t mask_lock; }; struct psr_assoc { @@ -48,9 +142,7 @@ struct psr_assoc { struct psr_cmt *__read_mostly psr_cmt; -static unsigned long *__read_mostly cat_socket_enable; -static struct psr_cat_socket_info *__read_mostly cat_socket_info; -static unsigned long *__read_mostly cdp_socket_enable; +static struct psr_socket_alloc_info *__read_mostly socket_alloc_info; static unsigned int opt_psr; static unsigned int __initdata opt_rmid_max = 255; @@ -58,8 +150,427 @@ static unsigned int __read_mostly opt_cos_max = 255; static uint64_t rmid_mask; static DEFINE_PER_CPU(struct psr_assoc, psr_assoc); -static struct psr_cat_cbm *temp_cos_to_cbm; +static struct psr_ref *temp_cos_ref; +/* Every feature has its own object. */ +struct feat_list *pL3CAT; + +/* Common functions for supporting feature callback functions. */ +static void add_feature(struct feat_list *pHead, struct feat_list *pTmp) +{ + if ( NULL == pHead || NULL == pTmp ) + return; + + while ( pHead->pNext ) + pHead = pHead->pNext; + + pHead->pNext = pTmp; +} + +static void free_feature(struct psr_socket_alloc_info *info) +{ + struct feat_list *pTmp; + struct feat_list *pPrev; + + if ( NULL == info ) + return; + + if ( NULL == info->pFeat ) + return; + + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + pPrev = pTmp; + pTmp = pTmp->pNext; + clear_bit(pPrev->feature, &(info->features)); + xfree(pPrev); + pPrev = NULL; + } +} + +static bool_t psr_check_cbm(unsigned int cbm_len, uint64_t cbm) +{ + unsigned int first_bit, zero_bit; + + /* Set bits should only in the range of [0, cbm_len). */ + if ( cbm & (~0ull << cbm_len) ) + return 0; + + /* At least one bit need to be set. */ + if ( cbm == 0 ) + return 0; + + first_bit = find_first_bit(&cbm, cbm_len); + zero_bit = find_next_zero_bit(&cbm, cbm_len, first_bit); + + /* Set bits should be contiguous. */ + if ( zero_bit < cbm_len && + find_next_bit(&cbm, cbm_len, zero_bit) < cbm_len ) + return 0; + + return 1; +} + +/* + * Features specific implementations. + */ + +/* CAT/CDP data structure and callback functions implementation. */ +struct psr_cat_lvl_info { + unsigned int cbm_len; + unsigned int cos_max; +}; + +static void l3_cat_init_feature(unsigned int eax, unsigned int ebx, + unsigned int ecx, unsigned int edx, + struct feat_list *pFeat, + struct psr_socket_alloc_info *info) +{ + struct psr_cat_lvl_info l3_cat; + unsigned int socket; + uint64_t val; + + if ( MAX_FEAT_INFO_SIZE < sizeof(struct psr_cat_lvl_info) ) + return; + + /* No valid value so do not enable feature. */ + if ( 0 == eax || 0 == edx ) + return; + + l3_cat.cbm_len = (eax & 0x1f) + 1; + l3_cat.cos_max = min(opt_cos_max, edx & 0xffff); + /* cos=0 is reserved as default cbm(all ones). */ + pFeat->cos_reg_val[0] = (1ull << l3_cat.cbm_len) - 1; + + pFeat->feature = PSR_SOCKET_L3_CAT; + set_bit(PSR_SOCKET_L3_CAT, &(info->features)); + + if ( (ecx & PSR_CAT_CDP_CAPABILITY) && (opt_psr & PSR_CDP) && + !test_bit(PSR_SOCKET_L3_CDP, &(info->features)) ) + { + /* Data */ + pFeat->cos_reg_val[0] = (1ull << l3_cat.cbm_len) - 1; + /* Code */ + pFeat->cos_reg_val[1] = (1ull << l3_cat.cbm_len) - 1; + + /* We only write mask1 since mask0 is always all ones by default. */ + wrmsrl(MSR_IA32_PSR_L3_MASK(1), + (1ull << l3_cat.cbm_len) - 1); + rdmsrl(MSR_IA32_PSR_L3_QOS_CFG, val); + wrmsrl(MSR_IA32_PSR_L3_QOS_CFG, val | (1 << PSR_L3_QOS_CDP_ENABLE_BIT)); + + /* Cut half of cos_max when CDP is enabled. */ + l3_cat.cos_max >>= 1; + + pFeat->feature = PSR_SOCKET_L3_CDP; + set_bit(PSR_SOCKET_L3_CDP, &(info->features)); + clear_bit(PSR_SOCKET_L3_CAT, &(info->features)); + } + + memcpy(pFeat->feat_info, &l3_cat, sizeof(struct psr_cat_lvl_info)); + + info->nr_feat++; + + /* Add this feature into list. */ + add_feature(info->pFeat, pFeat); + + socket = cpu_to_socket(smp_processor_id()); + printk(XENLOG_INFO "L3 CAT: enabled on socket %u, cos_max:%u, cbm_len:%u, CDP:%s\n", + socket, pFeat->feat_info[1], pFeat->feat_info[0], + test_bit(PSR_SOCKET_L3_CDP, &(info->features)) ? "on" : "off"); +} + +static int l3_cat_compare_mask(uint64_t *mask, struct feat_list *pFeat, + unsigned int cos, bool_t *found) +{ + struct psr_cat_lvl_info cat_info; + uint64_t l3_def_cbm; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + l3_def_cbm = (1ull << cat_info.cbm_len) - 1; + + /* CDP */ + if ( pFeat->feature == PSR_SOCKET_L3_CDP ) + { + if ( cos > cat_info.cos_max ) + { + if ( mask[0] != l3_def_cbm || + mask[1] != l3_def_cbm ) + { + *found = 0; + printk(XENLOG_ERR "CDP exceed cos max.\n"); + return -ENOENT; + } + *found = 1; + return 2; + } + + if ( mask[0] == pFeat->cos_reg_val[cos * 2] && + mask[1] == pFeat->cos_reg_val[cos * 2 + 1]) + *found = 1; + else + *found = 0; + + return 2; + } + + /* CAT */ + if ( cos > cat_info.cos_max ) + { + if ( mask[0] != l3_def_cbm ) + { + *found = 0; + printk(XENLOG_ERR "CAT exceed cos max.\n"); + return -ENOENT; + } + *found = 1; + return 1; + } + + if ( mask[0] == pFeat->cos_reg_val[cos] ) + *found = 1; + else + *found = 0; + + return 1; +} + +static unsigned int l3_cat_get_cos_max_as_type(struct feat_list *pFeat, + enum mask_type type) +{ + struct psr_cat_lvl_info cat_info; + + if ( type != PSR_MASK_TYPE_L3_DATA && + type != PSR_MASK_TYPE_L3_CODE && + type != PSR_MASK_TYPE_L3_CBM ) + return 0; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + return cat_info.cos_max; +} + +static unsigned int l3_cat_exceed_range(uint64_t *mask, struct feat_list *pFeat, + unsigned int cos) +{ + struct psr_cat_lvl_info cat_info; + uint64_t l3_def_cbm; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + l3_def_cbm = (1ull << cat_info.cbm_len) - 1; + + /* CDP */ + if ( pFeat->feature == PSR_SOCKET_L3_CDP ) + { + if ( cos > cat_info.cos_max ) + if ( mask[0] != l3_def_cbm || + mask[1] != l3_def_cbm ) + /* + * Exceed cos_max and value to set is not default, + * return error. + */ + return 0; + + return 2; + } + + /* CAT */ + if ( cos > cat_info.cos_max ) + if ( mask[0] != l3_def_cbm ) + /* + * Exceed cos_max and value to set is not default, + * return error. + */ + return 0; + + return 1; +} + +static int l3_cat_write_msr(unsigned int cos, uint64_t *mask, + struct feat_list *pFeat) +{ + struct psr_cat_lvl_info cat_info; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + + /* CDP */ + if ( pFeat->feature == PSR_SOCKET_L3_CDP ) + { + /* + * If input cos is more than the cos_max of the feature, we should + * not set the value. + */ + if ( cos > cat_info.cos_max ) + return 2; + + /* Data */ + pFeat->cos_reg_val[cos * 2] = mask[0]; + /* Code */ + pFeat->cos_reg_val[cos * 2 + 1] = mask[1]; + + wrmsrl(MSR_IA32_PSR_L3_MASK_DATA(cos), mask[0]); + wrmsrl(MSR_IA32_PSR_L3_MASK_CODE(cos), mask[1]); + return 2; + } + + /* CAT */ + if ( cos > cat_info.cos_max ) + return 1; + + pFeat->cos_reg_val[cos] = mask[0]; + wrmsrl(MSR_IA32_PSR_L3_MASK(cos), mask[0]); + return 1; +} + +static int l3_cat_get_old_set_new(uint64_t *mask, + struct feat_list *pFeat, + unsigned int old_cos, + enum mask_type type, + uint64_t m) +{ + struct psr_cat_lvl_info cat_info; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + + if ( type == PSR_MASK_TYPE_L3_DATA || + type == PSR_MASK_TYPE_L3_CODE || + type == PSR_MASK_TYPE_L3_CBM ) + { + if ( !psr_check_cbm(cat_info.cbm_len, m) ) + return -EINVAL; + } + + if ( ( type == PSR_MASK_TYPE_L3_DATA || + type == PSR_MASK_TYPE_L3_CODE ) && + pFeat->feature != PSR_SOCKET_L3_CDP ) + return -ENXIO; + + /* CDP */ + if ( pFeat->feature == PSR_SOCKET_L3_CDP ) + { + if ( old_cos > cat_info.cos_max ) + { + /* Data */ + mask[0] = pFeat->cos_reg_val[0]; + /* Code */ + mask[1] = pFeat->cos_reg_val[1]; + } + else + { + /* Data */ + mask[0] = pFeat->cos_reg_val[old_cos * 2]; + /* Code */ + mask[1] = pFeat->cos_reg_val[old_cos * 2 + 1]; + } + + /* Set new mask */ + if ( type == PSR_MASK_TYPE_L3_DATA ) + mask[0] = m; + if ( type == PSR_MASK_TYPE_L3_CODE ) + mask[1] = m; + + return 2; + } + + /* CAT */ + if ( old_cos > cat_info.cos_max ) + mask[0] = pFeat->cos_reg_val[0]; + else + mask[0] = pFeat->cos_reg_val[old_cos]; + + if ( type == PSR_MASK_TYPE_L3_CBM ) + mask[0] = m; + + return 1; +} + +static int l3_cat_get_val(struct feat_list *pFeat, unsigned int cos, + enum mask_type type, uint64_t *val) +{ + struct psr_cat_lvl_info cat_info; + + if ( type != PSR_MASK_TYPE_L3_DATA && + type != PSR_MASK_TYPE_L3_CODE && + type != PSR_MASK_TYPE_L3_CBM ) + return 0; + + if ( ( type == PSR_MASK_TYPE_L3_DATA || + type == PSR_MASK_TYPE_L3_CODE ) && + pFeat->feature != PSR_SOCKET_L3_CDP ) + return -ENXIO; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + + /* CDP */ + if ( pFeat->feature == PSR_SOCKET_L3_CDP ) + { + if ( cos > cat_info.cos_max ) + *val = pFeat->cos_reg_val[0]; + else if ( type == PSR_MASK_TYPE_L3_DATA ) + *val = pFeat->cos_reg_val[cos * 2]; + else if ( type == PSR_MASK_TYPE_L3_CODE ) + *val = pFeat->cos_reg_val[cos * 2 + 1]; + + return 1; + } + + /* CAT */ + if ( cos > cat_info.cos_max ) + *val = pFeat->cos_reg_val[0]; + else + *val = pFeat->cos_reg_val[cos]; + + return 1; +} + +static int l3_cat_get_feat_info(struct feat_list *pFeat, enum mask_type type, + uint32_t *dat0, uint32_t *dat1, + uint32_t *dat2) +{ + struct psr_cat_lvl_info cat_info; + + if ( type != PSR_MASK_TYPE_L3_DATA && + type != PSR_MASK_TYPE_L3_CODE && + type != PSR_MASK_TYPE_L3_CBM ) + return 0; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + + *dat0 = cat_info.cbm_len; + *dat1 = cat_info.cos_max; + + if ( pFeat->feature == PSR_SOCKET_L3_CDP ) + *dat2 |= XEN_SYSCTL_PSR_CAT_L3_CDP; + else + *dat2 = 0; + + return 1; +} + +static unsigned int l3_cat_get_max_cos_max(struct feat_list *pFeat) +{ + struct psr_cat_lvl_info cat_info; + + memcpy(&cat_info, pFeat->feat_info, sizeof(struct psr_cat_lvl_info)); + + return cat_info.cos_max; +} + +struct feat_ops l3_cat_ops = { + .init_feature = l3_cat_init_feature, + .get_old_set_new = l3_cat_get_old_set_new, + .compare_mask = l3_cat_compare_mask, + .get_cos_max_as_type = l3_cat_get_cos_max_as_type, + .exceed_range = l3_cat_exceed_range, + .write_msr = l3_cat_write_msr, + .get_val = l3_cat_get_val, + .get_feat_info = l3_cat_get_feat_info, + .get_max_cos_max = l3_cat_get_max_cos_max, +}; + +/* + * Common functions implementation + */ static unsigned int get_socket_cpu(unsigned int socket) { if ( likely(socket < nr_sockets) ) @@ -209,17 +720,39 @@ void psr_free_rmid(struct domain *d) d->arch.psr_rmid = 0; } +static inline unsigned int get_max_cos_max(struct psr_socket_alloc_info *info) +{ + struct feat_list *pTmp; + unsigned int cos_max = 0; + + if ( NULL == info->pFeat || NULL == info->pFeat->pNext ) + return 0; + + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + cos_max = max(pTmp->ops.get_max_cos_max(pTmp), cos_max); + pTmp = pTmp->pNext; + } + + return cos_max; +} + static inline void psr_assoc_init(void) { struct psr_assoc *psra = &this_cpu(psr_assoc); + struct psr_socket_alloc_info *info; + unsigned int cos_max; + unsigned int socket; - if ( cat_socket_info ) + if ( socket_alloc_info ) { - unsigned int socket = cpu_to_socket(smp_processor_id()); + socket = cpu_to_socket(smp_processor_id()); + info = socket_alloc_info + socket; + cos_max = get_max_cos_max(info); - if ( test_bit(socket, cat_socket_enable) ) - psra->cos_mask = ((1ull << get_count_order( - cat_socket_info[socket].cos_max)) - 1) << 32; + if ( info->features ) + psra->cos_mask = ((1ull << get_count_order(cos_max)) - 1) << 32; } if ( psr_cmt_enabled() || psra->cos_mask ) @@ -256,270 +789,359 @@ void psr_ctxt_switch_to(struct domain *d) psra->val = reg; } } -static struct psr_cat_socket_info *get_cat_socket_info(unsigned int socket) + +static struct psr_socket_alloc_info *get_socket_alloc_info(unsigned int socket) { - if ( !cat_socket_info ) + struct psr_socket_alloc_info *info; + + if ( !socket_alloc_info ) return ERR_PTR(-ENODEV); if ( socket >= nr_sockets ) return ERR_PTR(-ENOTSOCK); - if ( !test_bit(socket, cat_socket_enable) ) - return ERR_PTR(-ENOENT); + info = socket_alloc_info + socket; - return cat_socket_info + socket; -} + if ( !info->features ) + return ERR_PTR(-ENOENT); -static inline bool_t cdp_is_enabled(unsigned int socket) -{ - return cdp_socket_enable && test_bit(socket, cdp_socket_enable); + return info; } -int psr_get_cat_l3_info(unsigned int socket, uint32_t *cbm_len, - uint32_t *cos_max, uint32_t *flags) +int psr_get_info(unsigned int socket, enum mask_type type, + uint32_t *dat0, uint32_t *dat1, + uint32_t *dat2) { - struct psr_cat_socket_info *info = get_cat_socket_info(socket); + struct psr_socket_alloc_info *info = get_socket_alloc_info(socket); + struct feat_list *pTmp; if ( IS_ERR(info) ) return PTR_ERR(info); - *cbm_len = info->cbm_len; - *cos_max = info->cos_max; + if ( NULL == info->pFeat || NULL == info->pFeat->pNext) + return -ENODEV; - *flags = 0; - if ( cdp_is_enabled(socket) ) - *flags |= XEN_SYSCTL_PSR_CAT_L3_CDP; + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + if ( pTmp->ops.get_feat_info(pTmp, type, dat0, dat1, dat2) ) + return 0; - return 0; + pTmp = pTmp->pNext; + } + + return -ENODEV; } -int psr_get_l3_cbm(struct domain *d, unsigned int socket, - uint64_t *cbm, enum cbm_type type) +static int get_old_set_new(uint64_t *mask, + struct psr_socket_alloc_info *info, + unsigned int old_cos, + enum mask_type type, + uint64_t m) { - struct psr_cat_socket_info *info = get_cat_socket_info(socket); - bool_t cdp_enabled = cdp_is_enabled(socket); - - if ( IS_ERR(info) ) - return PTR_ERR(info); + struct feat_list *pTmp; + int ret; - switch ( type ) - { - case PSR_CBM_TYPE_L3: - if ( cdp_enabled ) - return -EXDEV; - *cbm = info->cos_to_cbm[d->arch.psr_cos_ids[socket]].cbm; - break; + if ( !mask ) + return -EINVAL; - case PSR_CBM_TYPE_L3_CODE: - if ( !cdp_enabled ) - *cbm = info->cos_to_cbm[d->arch.psr_cos_ids[socket]].cbm; - else - *cbm = info->cos_to_cbm[d->arch.psr_cos_ids[socket]].code; - break; + if ( !info || !info->pFeat ) + return -ENODEV; - case PSR_CBM_TYPE_L3_DATA: - if ( !cdp_enabled ) - *cbm = info->cos_to_cbm[d->arch.psr_cos_ids[socket]].cbm; - else - *cbm = info->cos_to_cbm[d->arch.psr_cos_ids[socket]].data; - break; + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + /* mask getting and setting order is same as feature list */ + ret = pTmp->ops.get_old_set_new(mask, pTmp, old_cos, type, m); + if ( ret < 0 ) + return ret; - default: - ASSERT_UNREACHABLE(); + mask += ret; + pTmp = pTmp->pNext; } return 0; } -static bool_t psr_check_cbm(unsigned int cbm_len, uint64_t cbm) +int psr_get_val(struct domain *d, unsigned int socket, + uint64_t *val, enum mask_type type) { - unsigned int first_bit, zero_bit; - - /* Set bits should only in the range of [0, cbm_len). */ - if ( cbm & (~0ull << cbm_len) ) - return 0; + struct psr_socket_alloc_info *info = get_socket_alloc_info(socket); + unsigned int cos = d->arch.psr_cos_ids[socket]; + struct feat_list *pTmp; + int ret; - /* At least one bit need to be set. */ - if ( cbm == 0 ) - return 0; + if ( IS_ERR(info) ) + return PTR_ERR(info); - first_bit = find_first_bit(&cbm, cbm_len); - zero_bit = find_next_zero_bit(&cbm, cbm_len, first_bit); + if ( NULL == info->pFeat || NULL == info->pFeat->pNext ) + return -ENODEV; - /* Set bits should be contiguous. */ - if ( zero_bit < cbm_len && - find_next_bit(&cbm, cbm_len, zero_bit) < cbm_len ) - return 0; + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + ret = pTmp->ops.get_val(pTmp, cos, type, val); + if ( ret < 0 ) + return -EINVAL; + else if ( ret > 0 ) + /* Found */ + break; + + pTmp = pTmp->pNext; + } - return 1; + return 0; } -struct cos_cbm_info +struct cos_mask_info { unsigned int cos; - bool_t cdp; - uint64_t cbm_code; - uint64_t cbm_data; + struct feat_list *pFeat; + uint64_t *mask; }; -static void do_write_l3_cbm(void *data) +static void do_write_psr_ref(void *data) { - struct cos_cbm_info *info = data; + struct cos_mask_info *info = (struct cos_mask_info *)data; + unsigned int cos = info->cos; + struct feat_list *pFeat = info->pFeat; + uint64_t *mask = info->mask; + struct feat_list *pTmp; + int ret; + + if ( NULL == pFeat || NULL == pFeat->pNext) + return; - if ( info->cdp ) + pTmp = pFeat->pNext; + while ( pTmp ) { - wrmsrl(MSR_IA32_PSR_L3_MASK_CODE(info->cos), info->cbm_code); - wrmsrl(MSR_IA32_PSR_L3_MASK_DATA(info->cos), info->cbm_data); + ret = pTmp->ops.write_msr(cos, mask, pTmp); + if ( ret <= 0) + return; + + mask += ret; + pTmp = pTmp->pNext; } - else - wrmsrl(MSR_IA32_PSR_L3_MASK(info->cos), info->cbm_code); } -static int write_l3_cbm(unsigned int socket, unsigned int cos, - uint64_t cbm_code, uint64_t cbm_data, bool_t cdp) +static int write_psr_ref(unsigned int socket, unsigned int cos, + uint64_t *mask) { - struct cos_cbm_info info = + struct psr_socket_alloc_info *info = get_socket_alloc_info(socket); + + struct cos_mask_info data = { .cos = cos, - .cbm_code = cbm_code, - .cbm_data = cbm_data, - .cdp = cdp, + .pFeat = info->pFeat, + .mask = mask, }; if ( socket == cpu_to_socket(smp_processor_id()) ) - do_write_l3_cbm(&info); + do_write_psr_ref(&data); else { unsigned int cpu = get_socket_cpu(socket); if ( cpu >= nr_cpu_ids ) return -ENOTSOCK; - on_selected_cpus(cpumask_of(cpu), do_write_l3_cbm, &info, 1); + on_selected_cpus(cpumask_of(cpu), do_write_psr_ref, &data, 1); } return 0; } -static int find_cos(struct psr_cat_cbm *map, unsigned int cos_max, - uint64_t cbm_code, uint64_t cbm_data, bool_t cdp_enabled) +static int find_cos(uint64_t *mask, enum mask_type type, + struct psr_socket_alloc_info *info) { unsigned int cos; + struct psr_ref *map = info->cos_ref; + struct feat_list *pTmp; + uint64_t *pMask = mask; + int ret; + bool_t found = 0; + unsigned int cos_max = 0; + + /* cos_max is the max COS of the feature to be set. */ + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + cos_max = pTmp->ops.get_cos_max_as_type(pTmp, type); + if ( cos_max > 0 ) + break; + + pTmp = pTmp->pNext; + } + pTmp = info->pFeat->pNext; for ( cos = 0; cos <= cos_max; cos++ ) { - if ( (map[cos].ref || cos == 0) && - ((!cdp_enabled && map[cos].cbm == cbm_code) || - (cdp_enabled && map[cos].code == cbm_code && - map[cos].data == cbm_data)) ) + if ( 0 != cos && 0 == map[cos].ref ) + continue; + + while ( pTmp ) + { + /* + * Compare mask according to feature list order. + * We must follow this order because mask is assembled + * as this order in get_old_set_new(). + */ + ret = pTmp->ops.compare_mask(pMask, pTmp, cos, &found); + if ( ret < 0 ) + return ret; + + /* If fail to match, go to next cos to compare. */ + if ( !found ) + break; + + pMask += ret; + pTmp = pTmp->pNext; + } + + /* Every feature can match, this cos is what we find. */ + if ( found ) return cos; + + /* Not found, need find again from beginning. */ + pTmp = info->pFeat->pNext; + pMask = mask; } + printk(XENLOG_INFO "%s: cannot find cos.\n", __func__); return -ENOENT; } -static int pick_avail_cos(struct psr_cat_cbm *map, unsigned int cos_max, - unsigned int old_cos) +static int check_exceed_range(uint64_t *mask, struct feat_list *pTmp, + unsigned int cos) { + unsigned int ret = 0; + + while ( pTmp ) + { + ret = pTmp->ops.exceed_range(mask, pTmp, cos); + if ( 0 == ret ) + return -ENOENT; + + mask += ret; + pTmp = pTmp->pNext; + } + + return 0; +} + +static int alloc_new_cos(struct psr_socket_alloc_info *info, + uint64_t *mask, unsigned int old_cos, + enum mask_type type) +{ + unsigned int cos_max = 0; + struct feat_list *pTmp; unsigned int cos; + struct psr_ref *map = info->cos_ref; + + /* + * cos_max is the max COS of the feature to be set. + */ + pTmp = info->pFeat->pNext; + while ( pTmp ) + { + cos_max = pTmp->ops.get_cos_max_as_type(pTmp, type); + if ( cos_max > 0 ) + break; + + pTmp = pTmp->pNext; + } + + if ( !cos_max ) + return -ENOENT; /* If old cos is referred only by the domain, then use it. */ if ( map[old_cos].ref == 1 && old_cos != 0 ) + { + pTmp = info->pFeat->pNext; + if ( check_exceed_range(mask, pTmp, old_cos) ) + goto find_new; + return old_cos; + } +find_new: /* Find an unused one other than cos0. */ for ( cos = 1; cos <= cos_max; cos++ ) + /* + * ref is 0 means this COS is not occupied by other domain and + * can be used for current setting. + */ if ( map[cos].ref == 0 ) + { + pTmp = info->pFeat->pNext; + if ( check_exceed_range(mask, pTmp, cos) ) + return -ENOENT; + return cos; + } + printk(XENLOG_INFO "%s: all COSs have been occupied.\n", __func__); return -ENOENT; } -int psr_set_l3_cbm(struct domain *d, unsigned int socket, - uint64_t cbm, enum cbm_type type) +int psr_set_val(struct domain *d, unsigned int socket, + uint64_t val, enum mask_type type) { - unsigned int old_cos, cos_max; + unsigned int old_cos; int cos, ret; - uint64_t cbm_data, cbm_code; - bool_t cdp_enabled = cdp_is_enabled(socket); - struct psr_cat_cbm *map; - struct psr_cat_socket_info *info = get_cat_socket_info(socket); + struct psr_ref *map; + uint64_t *mask; + struct psr_socket_alloc_info *info = get_socket_alloc_info(socket); if ( IS_ERR(info) ) return PTR_ERR(info); - if ( !psr_check_cbm(info->cbm_len, cbm) ) - return -EINVAL; - - if ( !cdp_enabled && (type == PSR_CBM_TYPE_L3_CODE || - type == PSR_CBM_TYPE_L3_DATA) ) - return -ENXIO; - - cos_max = info->cos_max; + map = info->cos_ref; old_cos = d->arch.psr_cos_ids[socket]; - map = info->cos_to_cbm; - - switch ( type ) - { - case PSR_CBM_TYPE_L3: - cbm_code = cbm; - cbm_data = cbm; - break; - - case PSR_CBM_TYPE_L3_CODE: - cbm_code = cbm; - cbm_data = map[old_cos].data; - break; - - case PSR_CBM_TYPE_L3_DATA: - cbm_code = map[old_cos].code; - cbm_data = cbm; - break; - default: - ASSERT_UNREACHABLE(); - return -EINVAL; - } + /* Considering CDP liking features */ + mask = xzalloc_array(uint64_t, info->nr_feat * 2); + if ( (ret = get_old_set_new(mask, info, old_cos, type, val)) != 0 ) + return ret; - spin_lock(&info->cbm_lock); - cos = find_cos(map, cos_max, cbm_code, cbm_data, cdp_enabled); + spin_lock(&info->mask_lock); + cos = find_cos(mask, type, info); if ( cos >= 0 ) { if ( cos == old_cos ) { - spin_unlock(&info->cbm_lock); + spin_unlock(&info->mask_lock); + xfree(mask); return 0; } } else { - cos = pick_avail_cos(map, cos_max, old_cos); + cos = alloc_new_cos(info, mask, old_cos, type); if ( cos < 0 ) { - spin_unlock(&info->cbm_lock); + spin_unlock(&info->mask_lock); + xfree(mask); return cos; } - /* We try to avoid writing MSR. */ - if ( (cdp_enabled && - (map[cos].code != cbm_code || map[cos].data != cbm_data)) || - (!cdp_enabled && map[cos].cbm != cbm_code) ) + /* Write all features mask MSRs corresponding to the COS */ + ret = write_psr_ref(socket, cos, mask); + if ( ret ) { - ret = write_l3_cbm(socket, cos, cbm_code, cbm_data, cdp_enabled); - if ( ret ) - { - spin_unlock(&info->cbm_lock); - return ret; - } - map[cos].code = cbm_code; - map[cos].data = cbm_data; + spin_unlock(&info->mask_lock); + xfree(mask); + return ret; } } map[cos].ref++; map[old_cos].ref--; - spin_unlock(&info->cbm_lock); + + spin_unlock(&info->mask_lock); d->arch.psr_cos_ids[socket] = cos; + xfree(mask); + mask = NULL; return 0; } @@ -529,20 +1151,23 @@ static void psr_free_cos(struct domain *d) { unsigned int socket; unsigned int cos; - struct psr_cat_socket_info *info; + struct psr_socket_alloc_info *info; if( !d->arch.psr_cos_ids ) return; - for_each_set_bit(socket, cat_socket_enable, nr_sockets) + for( socket = 0; socket < nr_sockets; socket++) { if ( (cos = d->arch.psr_cos_ids[socket]) == 0 ) continue; - info = cat_socket_info + socket; - spin_lock(&info->cbm_lock); - info->cos_to_cbm[cos].ref--; - spin_unlock(&info->cbm_lock); + info = get_socket_alloc_info(socket); + if ( IS_ERR(info) ) + continue; + + spin_lock(&info->mask_lock); + info->cos_ref[cos].ref--; + spin_unlock(&info->mask_lock); } xfree(d->arch.psr_cos_ids); @@ -551,7 +1176,7 @@ static void psr_free_cos(struct domain *d) int psr_domain_init(struct domain *d) { - if ( cat_socket_info ) + if ( socket_alloc_info ) { d->arch.psr_cos_ids = xzalloc_array(unsigned int, nr_sockets); if ( !d->arch.psr_cos_ids ) @@ -567,137 +1192,147 @@ void psr_domain_free(struct domain *d) psr_free_cos(d); } -static int cat_cpu_prepare(unsigned int cpu) +static int internal_cpu_prepare(unsigned int cpu) { - if ( !cat_socket_info ) + if ( !socket_alloc_info ) return 0; - if ( temp_cos_to_cbm == NULL && - (temp_cos_to_cbm = xzalloc_array(struct psr_cat_cbm, + if ( temp_cos_ref == NULL && + (temp_cos_ref = xzalloc_array(struct psr_ref, opt_cos_max + 1UL)) == NULL ) return -ENOMEM; + /* Malloc memory for the global feature head here. */ + if ( pL3CAT == NULL && + (pL3CAT = xzalloc(struct feat_list)) == NULL ) + return -ENOMEM; + return 0; } -static void cat_cpu_init(void) +static void internal_cpu_init(void) { unsigned int eax, ebx, ecx, edx; - struct psr_cat_socket_info *info; + struct psr_socket_alloc_info *info; unsigned int socket; unsigned int cpu = smp_processor_id(); - uint64_t val; const struct cpuinfo_x86 *c = cpu_data + cpu; + struct feat_list *pTmp; if ( !cpu_has(c, X86_FEATURE_PQE) || c->cpuid_level < PSR_CPUID_LEVEL_CAT ) return; socket = cpu_to_socket(cpu); - if ( test_bit(socket, cat_socket_enable) ) + info = socket_alloc_info + socket; + if ( info->features ) return; cpuid_count(PSR_CPUID_LEVEL_CAT, 0, &eax, &ebx, &ecx, &edx); if ( ebx & PSR_RESOURCE_TYPE_L3 ) { - cpuid_count(PSR_CPUID_LEVEL_CAT, 1, &eax, &ebx, &ecx, &edx); - info = cat_socket_info + socket; - info->cbm_len = (eax & 0x1f) + 1; - info->cos_max = min(opt_cos_max, edx & 0xffff); - - info->cos_to_cbm = temp_cos_to_cbm; - temp_cos_to_cbm = NULL; - /* cos=0 is reserved as default cbm(all ones). */ - info->cos_to_cbm[0].cbm = (1ull << info->cbm_len) - 1; + pTmp = pL3CAT; + if ( !pTmp ) + return; + pL3CAT = NULL; - spin_lock_init(&info->cbm_lock); - - set_bit(socket, cat_socket_enable); - - if ( (ecx & PSR_CAT_CDP_CAPABILITY) && (opt_psr & PSR_CDP) && - cdp_socket_enable && !test_bit(socket, cdp_socket_enable) ) - { - info->cos_to_cbm[0].code = (1ull << info->cbm_len) - 1; - info->cos_to_cbm[0].data = (1ull << info->cbm_len) - 1; - - /* We only write mask1 since mask0 is always all ones by default. */ - wrmsrl(MSR_IA32_PSR_L3_MASK(1), (1ull << info->cbm_len) - 1); - - rdmsrl(MSR_IA32_PSR_L3_QOS_CFG, val); - wrmsrl(MSR_IA32_PSR_L3_QOS_CFG, val | (1 << PSR_L3_QOS_CDP_ENABLE_BIT)); - - /* Cut half of cos_max when CDP is enabled. */ - info->cos_max >>= 1; - - set_bit(socket, cdp_socket_enable); - } - printk(XENLOG_INFO "CAT: enabled on socket %u, cos_max:%u, cbm_len:%u, CDP:%s\n", - socket, info->cos_max, info->cbm_len, - cdp_is_enabled(socket) ? "on" : "off"); + /* Initialize this feature according to CPUID. */ + cpuid_count(PSR_CPUID_LEVEL_CAT, 1, &eax, &ebx, &ecx, &edx); + pTmp->ops = l3_cat_ops; + pTmp->ops.init_feature(eax, ebx, ecx, edx, pTmp, info); } } -static void cat_cpu_fini(unsigned int cpu) +static void internal_cpu_fini(unsigned int cpu) { unsigned int socket = cpu_to_socket(cpu); if ( !socket_cpumask[socket] || cpumask_empty(socket_cpumask[socket]) ) { - struct psr_cat_socket_info *info = cat_socket_info + socket; + struct psr_socket_alloc_info *info = get_socket_alloc_info(socket); + + free_feature(info); - if ( info->cos_to_cbm ) + if ( info->cos_ref ) { - xfree(info->cos_to_cbm); - info->cos_to_cbm = NULL; + xfree(info->cos_ref); + info->cos_ref = NULL; } - - if ( cdp_is_enabled(socket) ) - clear_bit(socket, cdp_socket_enable); - - clear_bit(socket, cat_socket_enable); } } -static void __init psr_cat_free(void) +static void __init psr_free(void) { - xfree(cat_socket_enable); - cat_socket_enable = NULL; - xfree(cat_socket_info); - cat_socket_info = NULL; + int i; + + for ( i = 0; i < nr_sockets; i++ ) + { + free_feature(&socket_alloc_info[i]); + xfree(socket_alloc_info[i].pFeat); + socket_alloc_info[i].pFeat = NULL; + } + + xfree(socket_alloc_info); + socket_alloc_info = NULL; } -static void __init init_psr_cat(void) +static void __init init_psr(void) { + int i; + if ( opt_cos_max < 1 ) { printk(XENLOG_INFO "CAT: disabled, cos_max is too small\n"); return; } - cat_socket_enable = xzalloc_array(unsigned long, BITS_TO_LONGS(nr_sockets)); - cat_socket_info = xzalloc_array(struct psr_cat_socket_info, nr_sockets); - cdp_socket_enable = xzalloc_array(unsigned long, BITS_TO_LONGS(nr_sockets)); + socket_alloc_info = xzalloc_array(struct psr_socket_alloc_info, nr_sockets); + + if ( !socket_alloc_info ) + { + printk(XENLOG_INFO "Fail to alloc socket_alloc_info!\n"); + return; + } - if ( !cat_socket_enable || !cat_socket_info ) - psr_cat_free(); + for ( i = 0; i < nr_sockets; i++ ) + { + socket_alloc_info[i].pFeat = xzalloc(struct feat_list); + if ( NULL == socket_alloc_info[i].pFeat ) + { + printk(XENLOG_INFO "Fail to alloc pFeat!\n"); + return; + } + socket_alloc_info[i].pFeat->pNext = NULL; + } } static int psr_cpu_prepare(unsigned int cpu) { - return cat_cpu_prepare(cpu); + return internal_cpu_prepare(cpu); } static void psr_cpu_init(void) { - if ( cat_socket_info ) - cat_cpu_init(); + unsigned int socket; + struct psr_socket_alloc_info *info; + + if ( socket_alloc_info ) + { + socket = cpu_to_socket(smp_processor_id()); + info = socket_alloc_info + socket; + info->cos_ref = temp_cos_ref; + temp_cos_ref = NULL; + spin_lock_init(&info->mask_lock); + + internal_cpu_init(); + } psr_assoc_init(); } static void psr_cpu_fini(unsigned int cpu) { - if ( cat_socket_info ) - cat_cpu_fini(cpu); + if ( socket_alloc_info ) + internal_cpu_fini(cpu); } static int cpu_callback( @@ -739,13 +1374,13 @@ static int __init psr_presmp_init(void) init_psr_cmt(opt_rmid_max); if ( opt_psr & PSR_CAT ) - init_psr_cat(); + init_psr(); if ( psr_cpu_prepare(0) ) - psr_cat_free(); + psr_free(); psr_cpu_init(); - if ( psr_cmt_enabled() || cat_socket_info ) + if ( psr_cmt_enabled() || socket_alloc_info ) register_cpu_notifier(&cpu_nfb); return 0; diff --git a/xen/arch/x86/sysctl.c b/xen/arch/x86/sysctl.c index 14e7dc7..a143e7a 100644 --- a/xen/arch/x86/sysctl.c +++ b/xen/arch/x86/sysctl.c @@ -176,10 +176,11 @@ long arch_do_sysctl( switch ( sysctl->u.psr_cat_op.cmd ) { case XEN_SYSCTL_PSR_CAT_get_l3_info: - ret = psr_get_cat_l3_info(sysctl->u.psr_cat_op.target, - &sysctl->u.psr_cat_op.u.l3_info.cbm_len, - &sysctl->u.psr_cat_op.u.l3_info.cos_max, - &sysctl->u.psr_cat_op.u.l3_info.flags); + ret = psr_get_info(sysctl->u.psr_cat_op.target, + PSR_MASK_TYPE_L3_CBM, + &sysctl->u.psr_cat_op.u.l3_info.cbm_len, + &sysctl->u.psr_cat_op.u.l3_info.cos_max, + &sysctl->u.psr_cat_op.u.l3_info.flags); if ( !ret && __copy_field_to_guest(u_sysctl, sysctl, u.psr_cat_op) ) ret = -EFAULT; diff --git a/xen/include/asm-x86/psr.h b/xen/include/asm-x86/psr.h index 57f47e9..0993863 100644 --- a/xen/include/asm-x86/psr.h +++ b/xen/include/asm-x86/psr.h @@ -46,10 +46,10 @@ struct psr_cmt { struct psr_cmt_l3 l3; }; -enum cbm_type { - PSR_CBM_TYPE_L3, - PSR_CBM_TYPE_L3_CODE, - PSR_CBM_TYPE_L3_DATA, +enum mask_type { + PSR_MASK_TYPE_L3_CBM, + PSR_MASK_TYPE_L3_CODE, + PSR_MASK_TYPE_L3_DATA, }; extern struct psr_cmt *psr_cmt; @@ -63,12 +63,13 @@ int psr_alloc_rmid(struct domain *d); void psr_free_rmid(struct domain *d); void psr_ctxt_switch_to(struct domain *d); -int psr_get_cat_l3_info(unsigned int socket, uint32_t *cbm_len, - uint32_t *cos_max, uint32_t *flags); -int psr_get_l3_cbm(struct domain *d, unsigned int socket, - uint64_t *cbm, enum cbm_type type); -int psr_set_l3_cbm(struct domain *d, unsigned int socket, - uint64_t cbm, enum cbm_type type); +int psr_get_info(unsigned int socket, enum mask_type type, + uint32_t *dat0, uint32_t *dat1, + uint32_t *dat2); +int psr_get_val(struct domain *d, unsigned int socket, + uint64_t *val, enum mask_type type); +int psr_set_val(struct domain *d, unsigned int socket, + uint64_t val, enum mask_type type); int psr_domain_init(struct domain *d); void psr_domain_free(struct domain *d); -- 1.9.1 _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx https://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |