linux/kernel/memremap.c
/**
 * devm_memremap_pages - remap and provide memmap backing for the given resource
 * @dev: hosting device for @res
 * @pgmap: pointer to a struct dev_pgmap
 *
 * Notes:
 * 1/ At a minimum the res, ref and type members of @pgmap must be initialized
 *    by the caller before passing it to this function
 *
 * 2/ The altmap field may optionally be initialized, in which case altmap_valid
 *    must be set to true
 *
 * 3/ pgmap.ref must be 'live' on entry and 'dead' before devm_memunmap_pages()
 *    time (or devm release event). The expected order of events is that ref has
 *    been through percpu_ref_kill() before devm_memremap_pages_release(). The
 *    wait for the completion of all references being dropped and
 *    percpu_ref_exit() must occur after devm_memremap_pages_release().
 *
 * 4/ res is expected to be a host memory range that could feasibly be
 *    treated as a "System RAM" range, i.e. not a device mmio range, but
 *    this is not enforced.
 */
remap and provide memmap backing for the given resource
@dev: hosting device for @res
@pgmap: pointer to a struct dev_pgmap

1/ At a minimum the res, ref and type members of @pgmap must be initialized
by the caller before passing it to this function

2/ The altmap field may optionally be initialized, in which case altmap_valid
must be set to true

3/ pgmap.ref must be 'live' on entry and 'dead' before devm_memunmap_pages()
time (or devm release event). The expected order of events is that ref has
been through percpu_ref_kill() before devm_memremap_pages_release(). The
wait for the completion of all references being dropped and
percpu_ref_exit() must occur after devm_memremap_pages_release().

4/ res is expected to be a host memory range that could feasibly be
treated as a "System RAM" range, i.e. not a device mmio range, but
this is not enforced.
remap and provide memmap backing for the given resource @dev : hosting device for @res @pgmap : pointer to a struct dev_pgmap 1 / At a minimum the res ref and type members of @pgmap must be initialized by the caller before passing it to this function 2 / The altmap field may optionally be initialized in which case altmap_valid must be set to true 3 / pgmap . ref must be ' live ' on entry and ' dead ' before devm_memunmap_pages () time ( or devm release event ) . The expected order of events is that ref has been through percpu_ref_kill () before devm_memremap_pages_release () . The wait for the completion of all references being dropped and percpu_ref_exit () must occur after devm_memremap_pages_release () . 4 / res is expected to be a host memory range that could feasibly be treated as a " System RAM " range i . e . not a device mmio range but this is not enforced .
void *devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap)
{
	resource_size_t align_start, align_size, align_end;
	struct vmem_altmap *altmap = pgmap->altmap_valid ?
			&pgmap->altmap : NULL;
	struct resource *res = &pgmap->res;
	unsigned long pfn, pgoff, order;
	pgprot_t pgprot = PAGE_KERNEL;
	int error, nid, is_ram;
	struct dev_pagemap *conflict_pgmap;
	align_start = res->start & ~(SECTION_SIZE - 1);
	align_size = ALIGN(res->start + resource_size(res), SECTION_SIZE)
		- align_start;
	align_end = align_start + align_size - 1;
	conflict_pgmap = get_dev_pagemap(PHYS_PFN(align_start), NULL);
	if (conflict_pgmap) {
		dev_WARN(dev, "Conflicting mapping in same section\n");
		put_dev_pagemap(conflict_pgmap);
		return ERR_PTR(-ENOMEM);
	}
	conflict_pgmap = get_dev_pagemap(PHYS_PFN(align_end), NULL);
	if (conflict_pgmap) {
		dev_WARN(dev, "Conflicting mapping in same section\n");
		put_dev_pagemap(conflict_pgmap);
		return ERR_PTR(-ENOMEM);
	}
	is_ram = region_intersects(align_start, align_size,
		IORESOURCE_SYSTEM_RAM, IORES_DESC_NONE);
	if (is_ram == REGION_MIXED) {
		WARN_ONCE(1, "%s attempted on mixed region %pr\n",
				__func__, res);
		return ERR_PTR(-ENXIO);
	}
	if (is_ram == REGION_INTERSECTS)
		return __va(res->start);
	if (!pgmap->ref)
		return ERR_PTR(-EINVAL);
	pgmap->dev = dev;
	mutex_lock(&pgmap_lock);
	error = 0;
	foreach_order_pgoff(res, order, pgoff) {
		error = __radix_tree_insert(&pgmap_radix,
				PHYS_PFN(res->start) + pgoff, order, pgmap);
		if (error) {
			dev_err(dev, "%s: failed: %d\n", __func__, error);
			break;
		}
	}
	mutex_unlock(&pgmap_lock);
	if (error)
		goto err_radix;
	nid = dev_to_node(dev);
	if (nid < 0)
		nid = numa_mem_id();
	error = track_pfn_remap(NULL, &pgprot, PHYS_PFN(align_start), 0,
			align_size);
	if (error)
		goto err_pfn_remap;
	mem_hotplug_begin();
	error = kasan_add_zero_shadow(__va(align_start), align_size);
	if (error) {
		mem_hotplug_done();
		goto err_kasan;
	}
	error = arch_add_memory(nid, align_start, align_size, altmap, false);
	if (!error)
		move_pfn_range_to_zone(&NODE_DATA(nid)->node_zones[ZONE_DEVICE],
					align_start >> PAGE_SHIFT,
					align_size >> PAGE_SHIFT, altmap);
	mem_hotplug_done();
	if (error)
		goto err_add_memory;
	for_each_device_pfn(pfn, pgmap) {
		struct page *page = pfn_to_page(pfn);
		list_del(&page->lru);
		page->pgmap = pgmap;
		percpu_ref_get(pgmap->ref);
	}
	devm_add_action(dev, devm_memremap_pages_release, pgmap);
	return __va(res->start);
 err_add_memory:
	kasan_remove_zero_shadow(__va(align_start), align_size);
 err_kasan:
	untrack_pfn(NULL, PHYS_PFN(align_start), align_size);
 err_pfn_remap:
 err_radix:
	pgmap_radix_release(res, pgoff);
	return ERR_PTR(error);
}
void * devm_memremap_pages ( struct device * dev , struct dev_pagemap * pgmap ) { resource_size_t align_start , align_size , align_end ; struct vmem_altmap * altmap = pgmap -> altmap_valid ? & pgmap -> altmap : NULL ; struct resource * res = & pgmap -> res ; unsigned long pfn , pgoff , order ; pgprot_t pgprot = PAGE_KERNEL ; int error , nid , is_ram ; struct dev_pagemap * conflict_pgmap ; align_start = res -> start & ~ ( SECTION_SIZE - 1 ) ; align_size = ALIGN ( res -> start + resource_size ( res ) , SECTION_SIZE ) - align_start ; align_end = align_start + align_size - 1 ; conflict_pgmap = get_dev_pagemap ( PHYS_PFN ( align_start ) , NULL ) ; if ( conflict_pgmap ) { dev_WARN ( dev , " \n " ) ; put_dev_pagemap ( conflict_pgmap ) ; return ERR_PTR ( - ENOMEM ) ; } conflict_pgmap = get_dev_pagemap ( PHYS_PFN ( align_end ) , NULL ) ; if ( conflict_pgmap ) { dev_WARN ( dev , " \n " ) ; put_dev_pagemap ( conflict_pgmap ) ; return ERR_PTR ( - ENOMEM ) ; } is_ram = region_intersects ( align_start , align_size , IORESOURCE_SYSTEM_RAM , IORES_DESC_NONE ) ; if ( is_ram == REGION_MIXED ) { WARN_ONCE ( 1 , " \n " , __func__ , res ) ; return ERR_PTR ( - ENXIO ) ; } if ( is_ram == REGION_INTERSECTS ) return __va ( res -> start ) ; if ( ! pgmap -> ref ) return ERR_PTR ( - EINVAL ) ; pgmap -> dev = dev ; mutex_lock ( & pgmap_lock ) ; error = 0 ; foreach_order_pgoff ( res , order , pgoff ) { error = __radix_tree_insert ( & pgmap_radix , PHYS_PFN ( res -> start ) + pgoff , order , pgmap ) ; if ( error ) { dev_err ( dev , " \n " , __func__ , error ) ; break ; } } mutex_unlock ( & pgmap_lock ) ; if ( error ) goto err_radix ; nid = dev_to_node ( dev ) ; if ( nid < 0 ) nid = numa_mem_id ( ) ; error = track_pfn_remap ( NULL , & pgprot , PHYS_PFN ( align_start ) , 0 , align_size ) ; if ( error ) goto err_pfn_remap ; mem_hotplug_begin ( ) ; error = kasan_add_zero_shadow ( __va ( align_start ) , align_size ) ; if ( error ) { mem_hotplug_done ( ) ; goto err_kasan ; } error = arch_add_memory ( nid , align_start , align_size , altmap , false ) ; if ( ! error ) move_pfn_range_to_zone ( & NODE_DATA ( nid ) -> node_zones [ ZONE_DEVICE ] , align_start >> PAGE_SHIFT , align_size >> PAGE_SHIFT , altmap ) ; mem_hotplug_done ( ) ; if ( error ) goto err_add_memory ; for_each_device_pfn ( pfn , pgmap ) { struct page * page = pfn_to_page ( pfn ) ; list_del ( & page -> lru ) ; page -> pgmap = pgmap ; percpu_ref_get ( pgmap -> ref ) ; } devm_add_action ( dev , devm_memremap_pages_release , pgmap ) ; return __va ( res -> start ) ; err_add_memory : kasan_remove_zero_shadow ( __va ( align_start ) , align_size ) ; err_kasan : untrack_pfn ( NULL , PHYS_PFN ( align_start ) , align_size ) ; err_pfn_remap : err_radix : pgmap_radix_release ( res , pgoff ) ; return ERR_PTR ( error ) ; }
devm_memremap_pages - remap and provide memmap backing for the given resource
 @dev: hosting device for @res
 @pgmap: pointer to a struct dev_pgmap
devm_memremap_pages - remap and provide memmap backing for the given resource @dev : hosting device for @res @pgmap : pointer to a struct dev_pgmap
/*
		 * ZONE_DEVICE pages union ->lru with a ->pgmap back
		 * pointer.  It is a bug if a ZONE_DEVICE page is ever
		 * freed or placed on a driver-private list.  Seed the
		 * storage with LIST_POISON* values.
		 */