Re: [PATCH v2 4/6] kexec_file: Add mechanism to update kexec segments.
From: Andrew Morton <akpm@linux-foundation.org>
Date: 2016-08-15 22:28:03
Also in:
kexec, lkml
On Sat, 13 Aug 2016 00:18:23 -0300 Thiago Jung Bauermann [off-list ref] wrote:
quoted hunk ↗ jump to hunk
kexec_update_segment allows a given segment in kexec_image to have its contents updated. This is useful if the current kernel wants to send information to the next kernel that is up-to-date at the time of reboot. ...@@ -721,6 +721,105 @@ static struct page *kimage_alloc_page(struct kimage *image, return page; } +/** + * kexec_update_segment - update the contents of a kimage segment + * @buffer: New contents of the segment. + * @bufsz: @buffer size. + * @load_addr: Segment's physical address in the next kernel. + * @memsz: Segment size. + * + * This function assumes kexec_mutex is held. + * + * Return: 0 on success, negative errno on error. + */ +int kexec_update_segment(const char *buffer, unsigned long bufsz, + unsigned long load_addr, unsigned long memsz) +{ + int i; + unsigned long entry; + unsigned long *ptr = NULL; + void *dest = NULL; + + if (kexec_image == NULL) { + pr_err("Can't update segment: no kexec image loaded.\n"); + return -EINVAL; + } + + /* + * kexec_add_buffer rounds up segment sizes to PAGE_SIZE, so + * we have to do it here as well. + */ + memsz = ALIGN(memsz, PAGE_SIZE); + + for (i = 0; i < kexec_image->nr_segments; i++) + /* We only support updating whole segments. */ + if (load_addr == kexec_image->segment[i].mem && + memsz == kexec_image->segment[i].memsz) { + if (kexec_image->segment[i].do_checksum) { + pr_err("Trying to update non-modifiable segment.\n"); + return -EINVAL; + } + + break; + } + if (i == kexec_image->nr_segments) { + pr_err("Couldn't find segment to update: 0x%lx, size 0x%lx\n", + load_addr, memsz); + return -EINVAL; + } + + for (entry = kexec_image->head; !(entry & IND_DONE) && memsz; + entry = *ptr++) { + void *addr = (void *) (entry & PAGE_MASK); + + switch (entry & IND_FLAGS) { + case IND_DESTINATION: + dest = addr; + break; + case IND_INDIRECTION: + ptr = __va(addr); + break; + case IND_SOURCE: + /* Shouldn't happen, but verify just to be safe. */ + if (dest == NULL) { + pr_err("Invalid kexec entries list."); + return -EINVAL; + } + + if (dest == (void *) load_addr) { + struct page *page; + char *ptr; + size_t uchunk, mchunk; + + page = kmap_to_page(addr); + + ptr = kmap(page);
kmap_atomic() could be used here, and it is appreciably faster.
+ ptr += load_addr & ~PAGE_MASK;
+ mchunk = min_t(size_t, memsz,
+ PAGE_SIZE - (load_addr & ~PAGE_MASK));
+ uchunk = min(bufsz, mchunk);
+ memcpy(ptr, buffer, uchunk);
+
+ kunmap(page);
+
+ bufsz -= uchunk;
+ load_addr += mchunk;
+ buffer += mchunk;
+ memsz -= mchunk;
+ }
+ dest += PAGE_SIZE;
+ }
+
+ /* Shouldn't happen, but verify just to be safe. */
+ if (ptr == NULL) {
+ pr_err("Invalid kexec entries list.");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+