diff options
Diffstat (limited to 'drivers/iommu/amd_iommu_init.c')
| -rw-r--r-- | drivers/iommu/amd_iommu_init.c | 329 | 
1 files changed, 290 insertions, 39 deletions
diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index bf4959f4225b..9e0034196e10 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -44,7 +44,7 @@   */  #define IVRS_HEADER_LENGTH 48 -#define ACPI_IVHD_TYPE                  0x10 +#define ACPI_IVHD_TYPE_MAX_SUPPORTED	0x40  #define ACPI_IVMD_TYPE_ALL              0x20  #define ACPI_IVMD_TYPE                  0x21  #define ACPI_IVMD_TYPE_RANGE            0x22 @@ -58,6 +58,11 @@  #define IVHD_DEV_EXT_SELECT             0x46  #define IVHD_DEV_EXT_SELECT_RANGE       0x47  #define IVHD_DEV_SPECIAL		0x48 +#define IVHD_DEV_ACPI_HID		0xf0 + +#define UID_NOT_PRESENT                 0 +#define UID_IS_INTEGER                  1 +#define UID_IS_CHARACTER                2  #define IVHD_SPECIAL_IOAPIC		1  #define IVHD_SPECIAL_HPET		2 @@ -99,7 +104,11 @@ struct ivhd_header {  	u64 mmio_phys;  	u16 pci_seg;  	u16 info; -	u32 efr; +	u32 efr_attr; + +	/* Following only valid on IVHD type 11h and 40h */ +	u64 efr_reg; /* Exact copy of MMIO_EXT_FEATURES */ +	u64 res;  } __attribute__((packed));  /* @@ -111,6 +120,11 @@ struct ivhd_entry {  	u16 devid;  	u8 flags;  	u32 ext; +	u32 hidh; +	u64 cid; +	u8 uidf; +	u8 uidl; +	u8 uid;  } __attribute__((packed));  /* @@ -133,6 +147,7 @@ bool amd_iommu_irq_remap __read_mostly;  static bool amd_iommu_detected;  static bool __initdata amd_iommu_disabled; +static int amd_iommu_target_ivhd_type;  u16 amd_iommu_last_bdf;			/* largest PCI device id we have  					   to handle */ @@ -218,8 +233,12 @@ enum iommu_init_state {  #define EARLY_MAP_SIZE		4  static struct devid_map __initdata early_ioapic_map[EARLY_MAP_SIZE];  static struct devid_map __initdata early_hpet_map[EARLY_MAP_SIZE]; +static struct acpihid_map_entry __initdata early_acpihid_map[EARLY_MAP_SIZE]; +  static int __initdata early_ioapic_map_size;  static int __initdata early_hpet_map_size; +static int __initdata early_acpihid_map_size; +  static bool __initdata cmdline_maps;  static enum iommu_init_state init_state = IOMMU_START_STATE; @@ -394,6 +413,22 @@ static void __init iommu_unmap_mmio_space(struct amd_iommu *iommu)  	release_mem_region(iommu->mmio_phys, iommu->mmio_phys_end);  } +static inline u32 get_ivhd_header_size(struct ivhd_header *h) +{ +	u32 size = 0; + +	switch (h->type) { +	case 0x10: +		size = 24; +		break; +	case 0x11: +	case 0x40: +		size = 40; +		break; +	} +	return size; +} +  /****************************************************************************   *   * The functions below belong to the first pass of AMD IOMMU ACPI table @@ -408,7 +443,15 @@ static void __init iommu_unmap_mmio_space(struct amd_iommu *iommu)   */  static inline int ivhd_entry_length(u8 *ivhd)  { -	return 0x04 << (*ivhd >> 6); +	u32 type = ((struct ivhd_entry *)ivhd)->type; + +	if (type < 0x80) { +		return 0x04 << (*ivhd >> 6); +	} else if (type == IVHD_DEV_ACPI_HID) { +		/* For ACPI_HID, offset 21 is uid len */ +		return *((u8 *)ivhd + 21) + 22; +	} +	return 0;  }  /* @@ -420,7 +463,14 @@ static int __init find_last_devid_from_ivhd(struct ivhd_header *h)  	u8 *p = (void *)h, *end = (void *)h;  	struct ivhd_entry *dev; -	p += sizeof(*h); +	u32 ivhd_size = get_ivhd_header_size(h); + +	if (!ivhd_size) { +		pr_err("AMD-Vi: Unsupported IVHD type %#x\n", h->type); +		return -EINVAL; +	} + +	p += ivhd_size;  	end += h->length;  	while (p < end) { @@ -448,6 +498,22 @@ static int __init find_last_devid_from_ivhd(struct ivhd_header *h)  	return 0;  } +static int __init check_ivrs_checksum(struct acpi_table_header *table) +{ +	int i; +	u8 checksum = 0, *p = (u8 *)table; + +	for (i = 0; i < table->length; ++i) +		checksum += p[i]; +	if (checksum != 0) { +		/* ACPI table corrupt */ +		pr_err(FW_BUG "AMD-Vi: IVRS invalid checksum\n"); +		return -ENODEV; +	} + +	return 0; +} +  /*   * Iterate over all IVHD entries in the ACPI table and find the highest device   * id which we need to handle. This is the first of three functions which parse @@ -455,31 +521,19 @@ static int __init find_last_devid_from_ivhd(struct ivhd_header *h)   */  static int __init find_last_devid_acpi(struct acpi_table_header *table)  { -	int i; -	u8 checksum = 0, *p = (u8 *)table, *end = (u8 *)table; +	u8 *p = (u8 *)table, *end = (u8 *)table;  	struct ivhd_header *h; -	/* -	 * Validate checksum here so we don't need to do it when -	 * we actually parse the table -	 */ -	for (i = 0; i < table->length; ++i) -		checksum += p[i]; -	if (checksum != 0) -		/* ACPI table corrupt */ -		return -ENODEV; -  	p += IVRS_HEADER_LENGTH;  	end += table->length;  	while (p < end) {  		h = (struct ivhd_header *)p; -		switch (h->type) { -		case ACPI_IVHD_TYPE: -			find_last_devid_from_ivhd(h); -			break; -		default: -			break; +		if (h->type == amd_iommu_target_ivhd_type) { +			int ret = find_last_devid_from_ivhd(h); + +			if (ret) +				return ret;  		}  		p += h->length;  	} @@ -724,6 +778,42 @@ static int __init add_special_device(u8 type, u8 id, u16 *devid, bool cmd_line)  	return 0;  } +static int __init add_acpi_hid_device(u8 *hid, u8 *uid, u16 *devid, +				      bool cmd_line) +{ +	struct acpihid_map_entry *entry; +	struct list_head *list = &acpihid_map; + +	list_for_each_entry(entry, list, list) { +		if (strcmp(entry->hid, hid) || +		    (*uid && *entry->uid && strcmp(entry->uid, uid)) || +		    !entry->cmd_line) +			continue; + +		pr_info("AMD-Vi: Command-line override for hid:%s uid:%s\n", +			hid, uid); +		*devid = entry->devid; +		return 0; +	} + +	entry = kzalloc(sizeof(*entry), GFP_KERNEL); +	if (!entry) +		return -ENOMEM; + +	memcpy(entry->uid, uid, strlen(uid)); +	memcpy(entry->hid, hid, strlen(hid)); +	entry->devid = *devid; +	entry->cmd_line	= cmd_line; +	entry->root_devid = (entry->devid & (~0x7)); + +	pr_info("AMD-Vi:%s, add hid:%s, uid:%s, rdevid:%d\n", +		entry->cmd_line ? "cmd" : "ivrs", +		entry->hid, entry->uid, entry->root_devid); + +	list_add_tail(&entry->list, list); +	return 0; +} +  static int __init add_early_maps(void)  {  	int i, ret; @@ -746,6 +836,15 @@ static int __init add_early_maps(void)  			return ret;  	} +	for (i = 0; i < early_acpihid_map_size; ++i) { +		ret = add_acpi_hid_device(early_acpihid_map[i].hid, +					  early_acpihid_map[i].uid, +					  &early_acpihid_map[i].devid, +					  early_acpihid_map[i].cmd_line); +		if (ret) +			return ret; +	} +  	return 0;  } @@ -785,6 +884,7 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu,  	u32 dev_i, ext_flags = 0;  	bool alias = false;  	struct ivhd_entry *e; +	u32 ivhd_size;  	int ret; @@ -800,7 +900,14 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu,  	/*  	 * Done. Now parse the device entries  	 */ -	p += sizeof(struct ivhd_header); +	ivhd_size = get_ivhd_header_size(h); +	if (!ivhd_size) { +		pr_err("AMD-Vi: Unsupported IVHD type %#x\n", h->type); +		return -EINVAL; +	} + +	p += ivhd_size; +  	end += h->length; @@ -958,6 +1065,70 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu,  			break;  		} +		case IVHD_DEV_ACPI_HID: { +			u16 devid; +			u8 hid[ACPIHID_HID_LEN] = {0}; +			u8 uid[ACPIHID_UID_LEN] = {0}; +			int ret; + +			if (h->type != 0x40) { +				pr_err(FW_BUG "Invalid IVHD device type %#x\n", +				       e->type); +				break; +			} + +			memcpy(hid, (u8 *)(&e->ext), ACPIHID_HID_LEN - 1); +			hid[ACPIHID_HID_LEN - 1] = '\0'; + +			if (!(*hid)) { +				pr_err(FW_BUG "Invalid HID.\n"); +				break; +			} + +			switch (e->uidf) { +			case UID_NOT_PRESENT: + +				if (e->uidl != 0) +					pr_warn(FW_BUG "Invalid UID length.\n"); + +				break; +			case UID_IS_INTEGER: + +				sprintf(uid, "%d", e->uid); + +				break; +			case UID_IS_CHARACTER: + +				memcpy(uid, (u8 *)(&e->uid), ACPIHID_UID_LEN - 1); +				uid[ACPIHID_UID_LEN - 1] = '\0'; + +				break; +			default: +				break; +			} + +			DUMP_printk("  DEV_ACPI_HID(%s[%s])\t\tdevid: %02x:%02x.%x\n", +				    hid, uid, +				    PCI_BUS_NUM(devid), +				    PCI_SLOT(devid), +				    PCI_FUNC(devid)); + +			devid  = e->devid; +			flags = e->flags; + +			ret = add_acpi_hid_device(hid, uid, &devid, false); +			if (ret) +				return ret; + +			/* +			 * add_special_device might update the devid in case a +			 * command-line override is present. So call +			 * set_dev_entry_from_acpi after add_special_device. +			 */ +			set_dev_entry_from_acpi(iommu, devid, e->flags, 0); + +			break; +		}  		default:  			break;  		} @@ -1078,13 +1249,25 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)  	iommu->pci_seg = h->pci_seg;  	iommu->mmio_phys = h->mmio_phys; -	/* Check if IVHD EFR contains proper max banks/counters */ -	if ((h->efr != 0) && -	    ((h->efr & (0xF << 13)) != 0) && -	    ((h->efr & (0x3F << 17)) != 0)) { -		iommu->mmio_phys_end = MMIO_REG_END_OFFSET; -	} else { -		iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET; +	switch (h->type) { +	case 0x10: +		/* Check if IVHD EFR contains proper max banks/counters */ +		if ((h->efr_attr != 0) && +		    ((h->efr_attr & (0xF << 13)) != 0) && +		    ((h->efr_attr & (0x3F << 17)) != 0)) +			iommu->mmio_phys_end = MMIO_REG_END_OFFSET; +		else +			iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET; +		break; +	case 0x11: +	case 0x40: +		if (h->efr_reg & (1 << 9)) +			iommu->mmio_phys_end = MMIO_REG_END_OFFSET; +		else +			iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET; +		break; +	default: +		return -EINVAL;  	}  	iommu->mmio_base = iommu_map_mmio_space(iommu->mmio_phys, @@ -1117,6 +1300,32 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)  	return 0;  } +/** + * get_highest_supported_ivhd_type - Look up the appropriate IVHD type + * @ivrs          Pointer to the IVRS header + * + * This function search through all IVDB of the maximum supported IVHD + */ +static u8 get_highest_supported_ivhd_type(struct acpi_table_header *ivrs) +{ +	u8 *base = (u8 *)ivrs; +	struct ivhd_header *ivhd = (struct ivhd_header *) +					(base + IVRS_HEADER_LENGTH); +	u8 last_type = ivhd->type; +	u16 devid = ivhd->devid; + +	while (((u8 *)ivhd - base < ivrs->length) && +	       (ivhd->type <= ACPI_IVHD_TYPE_MAX_SUPPORTED)) { +		u8 *p = (u8 *) ivhd; + +		if (ivhd->devid == devid) +			last_type = ivhd->type; +		ivhd = (struct ivhd_header *)(p + ivhd->length); +	} + +	return last_type; +} +  /*   * Iterates over all IOMMU entries in the ACPI table, allocates the   * IOMMU structure and initializes it with init_iommu_one() @@ -1133,8 +1342,7 @@ static int __init init_iommu_all(struct acpi_table_header *table)  	while (p < end) {  		h = (struct ivhd_header *)p; -		switch (*p) { -		case ACPI_IVHD_TYPE: +		if (*p == amd_iommu_target_ivhd_type) {  			DUMP_printk("device: %02x:%02x.%01x cap: %04x "  				    "seg: %d flags: %01x info %04x\n", @@ -1151,9 +1359,6 @@ static int __init init_iommu_all(struct acpi_table_header *table)  			ret = init_iommu_one(iommu, h);  			if (ret)  				return ret; -			break; -		default: -			break;  		}  		p += h->length; @@ -1818,18 +2023,20 @@ static void __init free_dma_resources(void)   * remapping setup code.   *   * This function basically parses the ACPI table for AMD IOMMU (IVRS) - * three times: + * four times: + * + *	1 pass) Discover the most comprehensive IVHD type to use.   * - *	1 pass) Find the highest PCI device id the driver has to handle. + *	2 pass) Find the highest PCI device id the driver has to handle.   *		Upon this information the size of the data structures is   *		determined that needs to be allocated.   * - *	2 pass) Initialize the data structures just allocated with the + *	3 pass) Initialize the data structures just allocated with the   *		information in the ACPI table about available AMD IOMMUs   *		in the system. It also maps the PCI devices in the   *		system to specific IOMMUs   * - *	3 pass) After the basic data structures are allocated and + *	4 pass) After the basic data structures are allocated and   *		initialized we update them with information about memory   *		remapping requirements parsed out of the ACPI table in   *		this last pass. @@ -1857,6 +2064,17 @@ static int __init early_amd_iommu_init(void)  	}  	/* +	 * Validate checksum here so we don't need to do it when +	 * we actually parse the table +	 */ +	ret = check_ivrs_checksum(ivrs_base); +	if (ret) +		return ret; + +	amd_iommu_target_ivhd_type = get_highest_supported_ivhd_type(ivrs_base); +	DUMP_printk("Using IVHD type %#x\n", amd_iommu_target_ivhd_type); + +	/*  	 * First parse ACPI tables to find the largest Bus/Dev/Func  	 * we need to handle. Upon this information the shared data  	 * structures for the IOMMUs in the system will be allocated @@ -2259,10 +2477,43 @@ static int __init parse_ivrs_hpet(char *str)  	return 1;  } +static int __init parse_ivrs_acpihid(char *str) +{ +	u32 bus, dev, fn; +	char *hid, *uid, *p; +	char acpiid[ACPIHID_UID_LEN + ACPIHID_HID_LEN] = {0}; +	int ret, i; + +	ret = sscanf(str, "[%x:%x.%x]=%s", &bus, &dev, &fn, acpiid); +	if (ret != 4) { +		pr_err("AMD-Vi: Invalid command line: ivrs_acpihid(%s)\n", str); +		return 1; +	} + +	p = acpiid; +	hid = strsep(&p, ":"); +	uid = p; + +	if (!hid || !(*hid) || !uid) { +		pr_err("AMD-Vi: Invalid command line: hid or uid\n"); +		return 1; +	} + +	i = early_acpihid_map_size++; +	memcpy(early_acpihid_map[i].hid, hid, strlen(hid)); +	memcpy(early_acpihid_map[i].uid, uid, strlen(uid)); +	early_acpihid_map[i].devid = +		((bus & 0xff) << 8) | ((dev & 0x1f) << 3) | (fn & 0x7); +	early_acpihid_map[i].cmd_line	= true; + +	return 1; +} +  __setup("amd_iommu_dump",	parse_amd_iommu_dump);  __setup("amd_iommu=",		parse_amd_iommu_options);  __setup("ivrs_ioapic",		parse_ivrs_ioapic);  __setup("ivrs_hpet",		parse_ivrs_hpet); +__setup("ivrs_acpihid",		parse_ivrs_acpihid);  IOMMU_INIT_FINISH(amd_iommu_detect,  		  gart_iommu_hole_init,  |