static int __ref modify_pmd_table()

in mm/vmem.c [210:289]


static int __ref modify_pmd_table(pud_t *pud, unsigned long addr,
				  unsigned long end, bool add, bool direct)
{
	unsigned long next, prot, pages = 0;
	int ret = -ENOMEM;
	pmd_t *pmd;
	pte_t *pte;

	prot = pgprot_val(SEGMENT_KERNEL);
	if (!MACHINE_HAS_NX)
		prot &= ~_SEGMENT_ENTRY_NOEXEC;

	pmd = pmd_offset(pud, addr);
	for (; addr < end; addr = next, pmd++) {
		next = pmd_addr_end(addr, end);
		if (!add) {
			if (pmd_none(*pmd))
				continue;
			if (pmd_large(*pmd)) {
				if (IS_ALIGNED(addr, PMD_SIZE) &&
				    IS_ALIGNED(next, PMD_SIZE)) {
					if (!direct)
						vmem_free_pages(pmd_deref(*pmd), get_order(PMD_SIZE));
					pmd_clear(pmd);
					pages++;
				} else if (!direct && vmemmap_unuse_sub_pmd(addr, next)) {
					vmem_free_pages(pmd_deref(*pmd), get_order(PMD_SIZE));
					pmd_clear(pmd);
				}
				continue;
			}
		} else if (pmd_none(*pmd)) {
			if (IS_ALIGNED(addr, PMD_SIZE) &&
			    IS_ALIGNED(next, PMD_SIZE) &&
			    MACHINE_HAS_EDAT1 && addr && direct &&
			    !debug_pagealloc_enabled()) {
				pmd_val(*pmd) = __pa(addr) | prot;
				pages++;
				continue;
			} else if (!direct && MACHINE_HAS_EDAT1) {
				void *new_page;

				/*
				 * Use 1MB frames for vmemmap if available. We
				 * always use large frames even if they are only
				 * partially used. Otherwise we would have also
				 * page tables since vmemmap_populate gets
				 * called for each section separately.
				 */
				new_page = vmemmap_alloc_block(PMD_SIZE, NUMA_NO_NODE);
				if (new_page) {
					pmd_val(*pmd) = __pa(new_page) | prot;
					if (!IS_ALIGNED(addr, PMD_SIZE) ||
					    !IS_ALIGNED(next, PMD_SIZE)) {
						vmemmap_use_new_sub_pmd(addr, next);
					}
					continue;
				}
			}
			pte = vmem_pte_alloc();
			if (!pte)
				goto out;
			pmd_populate(&init_mm, pmd, pte);
		} else if (pmd_large(*pmd)) {
			if (!direct)
				vmemmap_use_sub_pmd(addr, next);
			continue;
		}
		ret = modify_pte_table(pmd, addr, next, add, direct);
		if (ret)
			goto out;
		if (!add)
			try_free_pte_table(pmd, addr & PMD_MASK);
	}
	ret = 0;
out:
	if (direct)
		update_page_count(PG_DIRECT_MAP_1M, add ? pages : -pages);
	return ret;
}