diff --git a/docs/misc/xen-command-line.markdown b/docs/misc/xen-command-line.markdown index 01389781e8..a11fdf9c23 100644 --- a/docs/misc/xen-command-line.markdown +++ b/docs/misc/xen-command-line.markdown @@ -1377,6 +1377,19 @@ Specify the host reboot method. 'efi' instructs Xen to reboot using the EFI reboot call (in EFI mode by default it will use that method first). +### rmrr +> '= start<-end>=[s1]bdf1[,[s1]bdf2[,...]];start<-end>=[s2]bdf1[,[s2]bdf2[,...]] + +Define RMRR units that are missing from ACPI table along with device they +belong to and use them for 1:1 mapping. End addresses can be omitted and one +page will be mapped. The ranges are inclusive when start and end are specified. +If segment of the first device is not specified, segment zero will be used. +If other segments are not specified, first device segment will be used. +If a segment is specified for other than the first device and it does not match +the one specified for the first one, an error will be reported. +Note: grub2 requires to escape or use quotations if special characters are used, +namely ';', refer to the grub2 documentation if multiple ranges are specified. + ### ro-hpet > `= ` diff --git a/xen/drivers/passthrough/vtd/dmar.c b/xen/drivers/passthrough/vtd/dmar.c index 3c7c9b2476..831fe1a33e 100644 --- a/xen/drivers/passthrough/vtd/dmar.c +++ b/xen/drivers/passthrough/vtd/dmar.c @@ -859,6 +859,134 @@ out: return ret; } +#define MAX_USER_RMRR_PAGES 16 +#define MAX_USER_RMRR 10 + +/* RMRR units derived from command line rmrr option. */ +#define MAX_USER_RMRR_DEV 20 +struct user_rmrr { + struct list_head list; + unsigned long base_pfn, end_pfn; + unsigned int dev_count; + u32 sbdf[MAX_USER_RMRR_DEV]; +}; + +static __initdata unsigned int nr_rmrr; +static struct __initdata user_rmrr user_rmrrs[MAX_USER_RMRR]; + +/* Macro for RMRR inclusive range formatting. */ +#define ERMRRU_FMT "[%lx-%lx]" +#define ERMRRU_ARG(eru) eru.base_pfn, eru.end_pfn + +static int __init add_user_rmrr(void) +{ + struct acpi_rmrr_unit *rmrr, *rmrru; + unsigned int idx, seg, i; + unsigned long base, end; + bool overlap; + + for ( i = 0; i < nr_rmrr; i++ ) + { + base = user_rmrrs[i].base_pfn; + end = user_rmrrs[i].end_pfn; + + if ( base > end ) + { + printk(XENLOG_ERR VTDPREFIX + "Invalid RMRR Range "ERMRRU_FMT"\n", + ERMRRU_ARG(user_rmrrs[i])); + continue; + } + + if ( (end - base) >= MAX_USER_RMRR_PAGES ) + { + printk(XENLOG_ERR VTDPREFIX + "RMRR range "ERMRRU_FMT" exceeds "\ + __stringify(MAX_USER_RMRR_PAGES)" pages\n", + ERMRRU_ARG(user_rmrrs[i])); + continue; + } + + overlap = false; + list_for_each_entry(rmrru, &acpi_rmrr_units, list) + { + if ( pfn_to_paddr(base) <= rmrru->end_address && + rmrru->base_address <= pfn_to_paddr(end) ) + { + printk(XENLOG_ERR VTDPREFIX + "Overlapping RMRRs: "ERMRRU_FMT" and [%lx-%lx]\n", + ERMRRU_ARG(user_rmrrs[i]), + paddr_to_pfn(rmrru->base_address), + paddr_to_pfn(rmrru->end_address)); + overlap = true; + break; + } + } + /* Don't add overlapping RMRR. */ + if ( overlap ) + continue; + + do + { + if ( !mfn_valid(base) ) + { + printk(XENLOG_ERR VTDPREFIX + "Invalid pfn in RMRR range "ERMRRU_FMT"\n", + ERMRRU_ARG(user_rmrrs[i])); + break; + } + } while ( base++ < end ); + + /* Invalid pfn in range as the loop ended before end_pfn was reached. */ + if ( base <= end ) + continue; + + rmrr = xzalloc(struct acpi_rmrr_unit); + if ( !rmrr ) + return -ENOMEM; + + rmrr->scope.devices = xmalloc_array(u16, user_rmrrs[i].dev_count); + if ( !rmrr->scope.devices ) + { + xfree(rmrr); + return -ENOMEM; + } + + seg = 0; + for ( idx = 0; idx < user_rmrrs[i].dev_count; idx++ ) + { + rmrr->scope.devices[idx] = user_rmrrs[i].sbdf[idx]; + seg |= PCI_SEG(user_rmrrs[i].sbdf[idx]); + } + if ( seg != PCI_SEG(user_rmrrs[i].sbdf[0]) ) + { + printk(XENLOG_ERR VTDPREFIX + "Segments are not equal for RMRR range "ERMRRU_FMT"\n", + ERMRRU_ARG(user_rmrrs[i])); + scope_devices_free(&rmrr->scope); + xfree(rmrr); + continue; + } + + rmrr->segment = seg; + rmrr->base_address = pfn_to_paddr(user_rmrrs[i].base_pfn); + /* Align the end_address to the end of the page */ + rmrr->end_address = pfn_to_paddr(user_rmrrs[i].end_pfn) | ~PAGE_MASK; + rmrr->scope.devices_cnt = user_rmrrs[i].dev_count; + + if ( register_one_rmrr(rmrr) ) + { + printk(XENLOG_ERR VTDPREFIX + "Could not register RMMR range "ERMRRU_FMT"\n", + ERMRRU_ARG(user_rmrrs[i])); + scope_devices_free(&rmrr->scope); + xfree(rmrr); + } + } + + return 0; +} + #include /* ACPI tables may not be DMA protected by tboot, so use DMAR copy */ /* SINIT saved in SinitMleData in TXT heap (which is DMA protected) */ @@ -868,6 +996,7 @@ int __init acpi_dmar_init(void) { acpi_physical_address dmar_addr; acpi_native_uint dmar_len; + int ret; if ( ACPI_SUCCESS(acpi_get_table_phys(ACPI_SIG_DMAR, 0, &dmar_addr, &dmar_len)) ) @@ -878,7 +1007,12 @@ int __init acpi_dmar_init(void) dmar_table = __va(dmar_addr); } - return parse_dmar_table(acpi_parse_dmar); + ret = parse_dmar_table(acpi_parse_dmar); + + if ( !ret ) + return add_user_rmrr(); + + return ret; } void acpi_dmar_reinstate(void) @@ -937,3 +1071,70 @@ int intel_iommu_get_reserved_device_memory(iommu_grdm_t *func, void *ctxt) return 0; } + +/* + * Parse rmrr Xen command line options and add parsed devices and regions into + * acpi_rmrr_unit list to mapped as RMRRs parsed from ACPI. + * Format: + * rmrr=start<-end>=[s1]bdf1[,[s1]bdf2[,...]];start<-end>=[s2]bdf1[,[s2]bdf2[,...]] + * If the segment of the first device is not specified, + * segment zero will be used. + * If other segments are not specified, first device segment will be used. + * If a segment is specified for other than the first device, and it does not + * match the one specified for the first one, an error will be reported. + */ +static void __init parse_rmrr_param(const char *str) +{ + const char *s = str, *cur, *stmp; + unsigned int seg, bus, dev, func, dev_count; + unsigned long start, end; + + do { + start = simple_strtoul(cur = s, &s, 0); + if ( cur == s ) + break; + + if ( *s == '-' ) + { + end = simple_strtoul(cur = s + 1, &s, 0); + if ( cur == s ) + break; + } + else + end = start; + + user_rmrrs[nr_rmrr].base_pfn = start; + user_rmrrs[nr_rmrr].end_pfn = end; + + if ( *s != '=' ) + continue; + + do { + bool def_seg = false; + + stmp = parse_pci_seg(s + 1, &seg, &bus, &dev, &func, &def_seg); + if ( !stmp ) + break; + + /* + * Not specified. + * Segment will be replaced with one from first device. + */ + if ( user_rmrrs[nr_rmrr].dev_count && def_seg ) + seg = PCI_SEG(user_rmrrs[nr_rmrr].sbdf[0]); + + /* Keep sbdf's even if they differ and later report an error. */ + dev_count = user_rmrrs[nr_rmrr].dev_count; + user_rmrrs[nr_rmrr].sbdf[dev_count] = PCI_SBDF(seg, bus, dev, func); + + user_rmrrs[nr_rmrr].dev_count++; + s = stmp; + } while ( *s == ',' && + user_rmrrs[nr_rmrr].dev_count < MAX_USER_RMRR_DEV ); + + if ( user_rmrrs[nr_rmrr].dev_count ) + nr_rmrr++; + + } while ( *s++ == ';' && nr_rmrr < MAX_USER_RMRR ); +} +custom_param("rmrr", parse_rmrr_param);