Conversion from kmap() to kmap_local_page()
Tags: linuxkerneldevelopment, outreachy, memorymanagement, operatingsystem
Published: 2023-07-14 18:20:27

Hello readers! I am currently in my seventh week of the Outreachy internship, where I am working as a Linux-Kernel intern on the project “Converting kmap() and kmap_atomic() call sites to kmap_local_page()”.

This blog is in continuation with my previous blog https://dev.to/heygauri/outreachy-series-think-about-your-audience-754. I have created another blog explaining a patch so that you all can get knowledge of the work I am doing.

The patch I will explain is categorized as ‘S’ (small) based on its difficulty level and implementation time. The link to the patch is given below.

>
https://lore.kernel.org/all/20230610175712.GA348514@sumitra.com/T/

In the file lib/test_hmm.c, the two functions “dmirror_do_read” and “dmirror_do_write” calls kmap(page)/kunmap(page). But the kmap() function has been deprecated. Read the blog https://dev.to/heygauri/outreachy-series-think-about-your-audience-754 to know the reasons behind the depreciation of kmap() and kmap_atomic() in favour of kmap_local(). This patch is created to replace the kmap() call with kmap_local_page().

Note you cannot directly replace the kmap()/kunmap() with kmap_local()/kunmap_local(). It is not that simple. Conversions to kmap_local_page() must follow the mapping restrictions imposed on kmap_local_page().

# Conversion from kmap() to kmap_local_page():

There are two checkpoints. 

- Firstly, to check whether the pages are from high memory.
 
Drivers, filesystems, and other non-core code are expected to call into the highmem system when a page _could_ be mapped in high memory. Many drivers call kmap*() “just to be sure”.

But part of this conversion is to determine if they really need to call kmap() or kmap_local_page() at all and eliminate those calls if the page is _known_ to _not_ be mapped in highmem. The below link to a patch created by my mentor Fabio is an example of such a case:

> https://lore.kernel.org/all/20220704140129.6463-1-fmdefrancesco@gmail.com 

- Secondly, to check, the return pointer validity should be local only. 

While kmap_local_page() is significantly faster than kmap(), for the highmem case, it comes with restrictions about the pointers validity. Contrary to kmap() mappings, the local mappings are only valid in the context of the caller and cannot be handed to other contexts. This implies that users must be absolutely sure to keep the use of the return address local to the thread which mapped it.

Also, we do not have to check for preemption and page faults in this conversion because kmap() creates mappings which are permanent and propagated across all the CPUs of the system and does not require the calling process to be in an atomic context like kmap_atomic() hence it does not disable preemption, page faults or migration.

Here, the pages can come from highmem and the mappings are also kept thread local in both the functions. Refer to the official highmem documentation for more information https://docs.kernel.org/mm/highmem.html

Apart from this, we can see that the code wrapped inside the kmap()/kunmap() in both functions is just giving a call to memcpy(ptr, tmp, PAGE_SIZE); and memcpy(tmp, ptr, PAGE_SIZE);

This memcpy() function copies a memory region to another using pointers and the specified fixed length. So the overall changes will look like

```
@@ static int dmirror_do_read(struct dmirror *dmirror, unsigned long start,
 	for (pfn = start >> PAGE_SHIFT; pfn < (end >> PAGE_SHIFT); pfn++) {
...

-		tmp = kmap(page);
+		tmp = kmap_local_page(page);
-		memcpy(ptr, tmp, PAGE_SIZE);
-		kunmap(page);
+		kunmap_local(tmp);

...
}
```

```
@@ static int dmirror_do_write(struct dmirror *dmirror, unsigned long start,
 	for (pfn = start >> PAGE_SHIFT; pfn < (end >> PAGE_SHIFT); pfn++) {
...

-		tmp = kmap(page);
+		tmp = kmap_local_page(page);
-		memcpy(tmp, ptr, PAGE_SIZE);
-		kunmap(page);
+		kunmap_local(tmp);

...
}
```

But we have some helper functions like memcpy_to_page() and memcpy_from_page() which replace kmap/mem*()/kunmap pattern with
memcpy_to/from_page(). Below is the function definition of these functions in which you can see the calls to kmap_local_page()/kunmap_local().

```
static inline void memcpy_from_page(char *to, struct page *page,
				    size_t offset, size_t len)
{
	char *from = kmap_local_page(page);

	VM_BUG_ON(offset + len > PAGE_SIZE);
	memcpy(to, from + offset, len);
	kunmap_local(from);
}

static inline void memcpy_to_page(struct page *page, size_t offset,
				  const char *from, size_t len)
{
	char *to = kmap_local_page(page);

	VM_BUG_ON(offset + len > PAGE_SIZE);
	memcpy(to + offset, from, len);
	flush_dcache_page(page);
	kunmap_local(to);
}
```

Additionally, remove the now unused variable void *tmp from both functions. This gives us our final changes for the patch.

```
@@ static int dmirror_do_read(struct dmirror *dmirror, unsigned long start,
 	for (pfn = start >> PAGE_SHIFT; pfn < (end >> PAGE_SHIFT); pfn++) {
...

 		void *entry;
 		struct page *page;
-		void *tmp;
 
 		entry = xa_load(&dmirror->pt, pfn);
 		page = xa_untag_pointer(entry);
 		if (!page)
 			return -ENOENT;
 
-		tmp = kmap(page);
-		memcpy(ptr, tmp, PAGE_SIZE);
-		kunmap(page);
+		memcpy_from_page((char *)ptr, page, 0, PAGE_SIZE);

...
}

```

```
@@ static int dmirror_do_write(struct dmirror *dmirror, unsigned long start,
 	for (pfn = start >> PAGE_SHIFT; pfn < (end >> PAGE_SHIFT); pfn++) {
...
 		void *entry;
 		struct page *page;
-		void *tmp;
 
 		entry = xa_load(&dmirror->pt, pfn);
 		page = xa_untag_pointer(entry);
 		if (!page || xa_pointer_tag(entry) != DPT_XA_TAG_WRITE)
 			return -ENOENT;
 
-		tmp = kmap(page);
-		memcpy(tmp, ptr, PAGE_SIZE);
-		kunmap(page);
+		memcpy_to_page(page, 0, (char *)ptr, PAGE_SIZE);
 
...
}
```

Lastly, compile and test any changes you make in the Linux kernel code before sending it to the maintainers.

I hope you learn something from this blog post. Let me know if you have queries related to this patch. Thank you for reading!