docs/mm: describe set_memory() and set_direct_map() APIs
Mike Rapoport (Microsoft)
reviewerPATCH 1/1 docs/mm: describe set_memory() and set_direct_map() APIs
The set_memory() and set_direct_map() APIs change permissions of existing kernel mappings, but their semantics are only described by the code, and that code differs from architecture to architecture.
Add Documentation/mm/kernel-page-tables.rst that briefly describes what the kernel page tables consist of, defines the semantics both APIs have in common, including the parts that are easy to get wrong, and lists the differences between the architecture implementations.
Add kernel-doc comments for the generic set_memory() and set_direct_map() stubs and link them into Documentation/core-api/mm-api.rst.
---
The semantics of set_memory() and set_direct_map() are currently described only by the code, and the code differs from architecture to architecture.
Document what these APIs actually promise, what they merely hint at, and how the architectures disagree.
The discussions that prompted this:
https://lore.kernel.org/all/[email protected]
https://lore.kernel.org/all/[email protected]
--- Documentation/core-api/mm-api.rst | 9 + Documentation/mm/index.rst | 1 + Documentation/mm/kernel-page-tables.rst | 402 ++++++++++++++++++++++++++++++++ include/linux/set_memory.h | 110 +++++++++ 4 files changed, 522 insertions(+)
Documentation/core-api/mm-api.rst +9 -0
@@ -52,6 +52,15 @@ Virtually Contiguous Mappings .. kernel-doc:: mm/vmalloc.c :export: +Kernel Page Table Permissions +============================= + +.. kernel-doc:: include/linux/set_memory.h + :doc: Kernel page table permissions + +.. kernel-doc:: include/linux/set_memory.h + :internal: + File Mapping and Page Cache ===========================
Documentation/mm/index.rst +1 -0
@@ -25,6 +25,7 @@ see the :doc:`admin guide <../admin-guide/mm/index>`. physical_memory page_tables + kernel-page-tables process_addrs bootmem page_allocation
Documentation/mm/kernel-page-tables.rst new file +402 -0
402 lines not on this page · raw message
include/linux/set_memory.h +110 -0
143 lines not on this page · raw message
---
base-commit: d118502628f8b673be9023db8bdf878f64a7ed45
change-id: 20260909-set-memory-docs-5f1d96c680a6
--
Sincerely yours,
Mike.
David Hildenbrand (Arm)
maintainerQuoted 21 lines · Mike Rapoport (Microsoft)
The
set_memory()andset_direct_map()APIs change permissions of existing kernel mappings, but their semantics are only described by the code, and that code differs from architecture to architecture.Add
Documentation/mm/kernel-page-tables.rstthat briefly describes what the kernel page tables consist of, defines the semantics both APIs have in common, including the parts that are easy to get wrong, and lists the differences between the architecture implementations.Add kernel-doc comments for the generic
set_memory()andset_direct_map()stubs and link them intoDocumentation/core-api/mm-api.rst.Assisted-by copilot:claude-opusSigned-off-by Mike Rapoport (Microsoft) <[email protected]>---
The semantics ofset_memory()andset_direct_map()are currently described only by the code, and the code differs from architecture to architecture.Document what these APIs actually promise, what they merely hint at, and how the architectures disagree.
Quoted 8 lines
The discussions that prompted this:
https://lore.kernel.org/all/[email protected]
https://lore.kernel.org/all/[email protected]--- Documentation/core-api/mm-api.rst | 9 + Documentation/mm/index.rst | 1 + Documentation/mm/kernel-page-tables.rst | 402 ++++++++++++++++++++++++++++++++
Before I read any of this, how much did you review and edit whatever the LLM came up with?
Signature · 3 lines
Cheers, David
Mike Rapoport
reviewerQuoted 31 lines · David Hildenbrand (Arm)
Quoted 21 lines · Mike Rapoport (Microsoft)
The
set_memory()andset_direct_map()APIs change permissions of existing kernel mappings, but their semantics are only described by the code, and that code differs from architecture to architecture.Add
Documentation/mm/kernel-page-tables.rstthat briefly describes what the kernel page tables consist of, defines the semantics both APIs have in common, including the parts that are easy to get wrong, and lists the differences between the architecture implementations.Add kernel-doc comments for the generic
set_memory()andset_direct_map()stubs and link them intoDocumentation/core-api/mm-api.rst.Assisted-by copilot:claude-opusSigned-off-by Mike Rapoport (Microsoft) <[email protected]>---
The semantics ofset_memory()andset_direct_map()are currently described only by the code, and the code differs from architecture to architecture.Document what these APIs actually promise, what they merely hint at, and how the architectures disagree.
Quoted 8 lines
The discussions that prompted this:
https://lore.kernel.org/all/[email protected]
https://lore.kernel.org/all/[email protected]--- Documentation/core-api/mm-api.rst | 9 + Documentation/mm/index.rst | 1 + Documentation/mm/kernel-page-tables.rst | 402 ++++++++++++++++++++++++++++++++
Quoted 2 lines
Before I read any of this, how much did you review and edit whatever the LLM came up with?
I never send something I don't like :)
Quoted 4 lines
Signature · 3 lines
Cheers, David
Signature · 2 lines
Sincerely yours, Mike.
David Hildenbrand (Arm)
maintainerQuoted 32 lines · Mike Rapoport
Quoted 31 lines · David Hildenbrand (Arm)
Quoted 29 lines · Mike Rapoport (Microsoft)
The
set_memory()andset_direct_map()APIs change permissions of existing kernel mappings, but their semantics are only described by the code, and that code differs from architecture to architecture.Add
Documentation/mm/kernel-page-tables.rstthat briefly describes what the kernel page tables consist of, defines the semantics both APIs have in common, including the parts that are easy to get wrong, and lists the differences between the architecture implementations.Add kernel-doc comments for the generic
set_memory()andset_direct_map()stubs and link them intoDocumentation/core-api/mm-api.rst.Assisted-by copilot:claude-opusSigned-off-by Mike Rapoport (Microsoft) <[email protected]>---
The semantics ofset_memory()andset_direct_map()are currently described only by the code, and the code differs from architecture to architecture.Document what these APIs actually promise, what they merely hint at, and how the architectures disagree.
The discussions that prompted this:
https://lore.kernel.org/all/[email protected]
https://lore.kernel.org/all/[email protected]--- Documentation/core-api/mm-api.rst | 9 + Documentation/mm/index.rst | 1 + Documentation/mm/kernel-page-tables.rst | 402 ++++++++++++++++++++++++++++++++
Quoted 4 lines
Quoted 2 lines
Before I read any of this, how much did you review and edit whatever the LLM came up with?
I never send something I don't like :)
Good. The rather excessive doc patch arrived rather fast so I had to ask :)
Signature · 3 lines
Cheers, David
Quoted 9 lines · Mike Rapoport (Microsoft)
The
set_memory()andset_direct_map()APIs change permissions of existing kernel mappings, but their semantics are only described by the code, and that code differs from architecture to architecture.Add
Documentation/mm/kernel-page-tables.rstthat briefly describes what the kernel page tables consist of, defines the semantics both APIs have in common, including the parts that are easy to get wrong, and lists the differences between the architecture implementations.
Quoted 5 lines
Add kernel-doc comments for the generic
set_memory()andset_direct_map()stubs and link them intoDocumentation/core-api/mm-api.rst.Assisted-by copilot:claude-opusSigned-off-by Mike Rapoport (Microsoft) <[email protected]>
Thanks for doing this Mike, I wish there had been such a document when I started using those APIs!
Much of that document feels like an excruciating FIXME list... which is exactly the state of set_memory/set_direct_map, and better to have it documented than letting every new user stumble upon the same gotchas.
Overall looks good to me, some minor comments below.
Quoted 10 lines
[...]
Documentation/mm/kernel-page-tables.rst new file +8
-0+Modifying the kernel page tables +================================ + +Except for the vmalloc area, the kernel page tables are mostly static. Still, +there are cases when the permissions of existing kernel mappings have to be +updated, for instance when a module is loaded and its text becomes read-only +and executable, or when a page is temporarily removed from the direct map to +reduce its exposure.
Documentation/mm/kernel-page-tables.rst line 108
Quoted 10 lines
Documentation/mm/kernel-page-tables.rst new file +10
-0+ +There are two families of functions for this, both declared in +`include/linux/set_memory.h`: + +* `set_memory_*()` change permissions of an arbitrary kernel mapping. They + take a kernel virtual address and the number of pages. + +* `set_direct_map_*()` change permissions of the direct map alias of a + `struct page`. They take a `struct page` pointer and the number of + pages.
"direct map alias of a struct page" is rather confusing, are we talking about the mapping of struct page itself?
Quoted 20 lines
Documentation/mm/kernel-page-tables.rst new file +18
-0+ +Architectures that implement `set_memory()` select `CONFIG_ARCH_HAS_SET_MEMORY` + +Architectures that implement `set_direct_map()` select +`CONFIG_ARCH_HAS_SET_DIRECT_MAP`. +Architecture specific differences +================================= + +The APIs are implemented by seven architectures and, beyond the common +semantics described above, their behaviour differs in several respects. + +Which of the APIs are implemented: + +========= ===================== ========================= +Arch `ARCH_HAS_SET_MEMORY` `ARCH_HAS_SET_DIRECT_MAP` +========= ===================== ========================= +arm yes no +arm64 yes yes
Documentation/mm/kernel-page-tables.rst line 274
Quoted 10 lines
Documentation/mm/kernel-page-tables.rst new file +10
-0+loongarch yes yes +powerpc yes no +riscv yes (MMU only) yes (MMU only) +s390 yes yes +x86 yes yes +========= ===================== ========================= + +Only set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() are +available everywhere, and even these are not universal: some architectures do +not implement any of them for the direct map, and the architectures that may
"do not implement any of them for the direct map" feels ambiguous - I think what we really mean is that they reject direct map addresses, e.g. what arm64 does (only accept addresses to kernel VMAs)?
Documentation/mm/kernel-page-tables.rst line 283
Quoted 9 lines
Documentation/mm/kernel-page-tables.rst new file +9
-0+run on hardware without an execute permission bit, like x86 and s390, silently +skip the update of the executable bit there. + +set_memory_rox() has a generic implementation that calls set_memory_ro() and +set_memory_x() in turn; PowerPC, s390 and x86 override it with a single-pass +version. + +Making a mapping present or not present is spelled differently: set_memory_p() +and set_memory_np() on x86 and PowerPC, set_memory_valid() on arm64 and arm.
I'm not sure we should even document set_memory_valid(). It doesn't at all behave like the other set_memory_* on amr64 (no check whatsoever, no handling of aliases) and is (fortunately) only used from arch code. I've been meaning to make its name scarier (__set_memory_valid?) for that reason.
Quoted 19 lines
Documentation/mm/kernel-page-tables.rst new file +19
-0+ +The direct map and the kernel image are normally mapped with the largest +possible pages, and changing the permissions of a single page inside such a +mapping requires splitting it, which not every architecture can do. + +arm +--- + +* Does not implement `set_direct_map()`. +* Provides set_memory_valid(). +* set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() accept + only vmalloc and module addresses. +* set_memory_valid() accepts any address. +* Does not update mapping aliases. + +arm64 +----- + +* Provides set_memory_valid().
Documentation/mm/kernel-page-tables.rst line 312
Quoted 10 lines
Documentation/mm/kernel-page-tables.rst new file +10
-0+* Provides the memory encryption helpers, which are effective only when the + kernel runs as a confidential guest. +* set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() accept + only vmalloc and module addresses: + + - the range must fit in the VM area that contains its start + - the VM area must have `VM_ALLOC` set and `VM_ALLOW_HUGE_VMAP` clear + +* set_memory_valid() accepts any address. +* The encryption helpers accept only the direct map addresses.
s/the//
Documentation/mm/kernel-page-tables.rst line 314
Quoted 2 lines
Documentation/mm/kernel-page-tables.rst new file +2
-0+* Propagates the read-only and the read-write changes to the direct map alias + when `rodata=full` is in effect.
That is, set_memory_<perms> propagate those changes.
Documentation/mm/kernel-page-tables.rst line 317
Quoted 3 lines
Documentation/mm/kernel-page-tables.rst new file +3
-0+* Splits leaf mappings before the update on the hardware that supports it. + Without such support an update that covers a leaf entry only partially fails + with a WARN()ing and `-EINVAL`.
What does "partially fails" mean? The splitting may be partial, but no permission change should occur.
Quoted 4 lines
Documentation/mm/kernel-page-tables.rst new file +4
-0+* The `set_direct_map()` functions return 0 without doing anything when the + direct map cannot be modified, see can_set_direct_map(). +* Skips the TLB flush in `set_memory()` when the update only turns an invalid + mapping into a valid one.
Documentation/mm/kernel-page-tables.rst line 331
Quoted 10 lines
Documentation/mm/kernel-page-tables.rst new file +10
-0+* Does not flush TLB in `set_direct_map()`. + +LoongArch +--------- + +* Accepts only the addresses above the hardware window and silently returns + success for the rest, see `Direct map`_. +* Does not update mapping aliases. +* Does not split anything: a leaf entry is updated as a whole, which changes + the permissions of the entire large mapping.
Ouch!
Quoted 57 lines
Documentation/mm/kernel-page-tables.rst new file +57
-0+* Flushes the TLB in `set_direct_map()`. + +PowerPC +------- + +* Does not implement `set_direct_map()`. +* Provides set_memory_np() and set_memory_p(). +* Rejects huge vmalloc mappings. +* With the hash MMU on 64-bit systems accepts nothing but the vmalloc and the + I/O regions. +* With the radix MMU accepts direct map addresses, but still cannot split a + large mapping. +* Does not update mapping aliases. + +riscv +----- + +* Implements both APIs only when the MMU is enabled. +* Provides set_memory_rw_nx(). +* The `set_memory()` functions accept any mapped kernel address, including the + direct map, but a vmalloc range must have the `pages` array of its VM area + populated, which rules out vmap() and ioremap() mappings. +* On 64-bit systems updates the direct map alias of a vmalloc range, including + the executable bit. +* Does not split vmalloc ranges: a leaf entry is updated as a whole, which + changes the permissions of the entire large mapping. +* Splits the direct map on 64-bit systems. +* Flushes the TLB in `set_direct_map()`. + +s390 +---- + +* Provides set_memory_4k(), set_memory_rwnx() and the + `__set_memory_*(start, end)` variants that take a range rather than a page + count. +* The `set_memory()` functions accept any mapped kernel address, including the + direct map. +* Skips the update of the executable bit when the hardware has no support for + it. +* Propagates only the read-only and read-write changes to the direct map alias + of a `VM_ALLOC` area, and deliberately not the executable bit. +* Splits leaf PUD and PMD entries when the range is not aligned to them or when + set_memory_4k() is requested. +* Updates the page table entries with instructions that invalidate the + corresponding TLB entries, so no separate flush is needed anywhere. + +x86 +--- + +* Provides the largest set of operations on top of the common ones: + + - the cache attribute helpers: set_memory_uc(), set_memory_wc(), + set_memory_wb() + - presence control: set_memory_np() and set_memory_p() + - set_memory_4k() + - set_memory_global() and set_memory_nonglobal() + - the array variants that operate on `struct page` arrays or arrays of
Documentation/mm/kernel-page-tables.rst line 398
Quoted 10 lines
Documentation/mm/kernel-page-tables.rst new file +10
-0+ virtual addresses + - memory encryption: set_memory_encrypted() and set_memory_decrypted() + +* The `set_memory()` functions accept any mapped kernel address, including the + direct map, and silently succeed for the unmapped holes inside it. +* Does nothing in set_memory_x() and set_memory_nx() when the CPU has no + execute permission bit. +* Applies the change to the direct map alias and, for the kernel image, to the + high kernel mapping. The NX bit is never propagated, so that the direct map + stays non-executable.
Same as above, bettermake the subject explicit (set_memory()?).
- Kevin
Quoted 6 lines
Documentation/mm/kernel-page-tables.rst new file +4
-0+* Splits large mappings on demand and can collapse them back when the + permissions become uniform again. +* Does not flush the TLB in `set_direct_map()`, but splitting a large + mapping flushes it anyway.
Mike Rapoport
reviewerQuoted 13 lines · Kevin Brodsky
Quoted 12 lines · Mike Rapoport (Microsoft)
The
set_memory()andset_direct_map()APIs change permissions of existing kernel mappings, but their semantics are only described by the code, and that code differs from architecture to architecture.Add
Documentation/mm/kernel-page-tables.rstthat briefly describes what the kernel page tables consist of, defines the semantics both APIs have in common, including the parts that are easy to get wrong, and lists the differences between the architecture implementations.Add kernel-doc comments for the generic
set_memory()andset_direct_map()stubs and link them intoDocumentation/core-api/mm-api.rst.
Quoted 7 lines
Quoted 2 lines
Assisted-by copilot:claude-opusSigned-off-by Mike Rapoport (Microsoft) <[email protected]>Thanks for doing this Mike, I wish there had been such a document when I started using those APIs!
Much of that document feels like an excruciating FIXME list... which is
It is :)
Quoted 20 lines
exactly the state of set_memory/set_direct_map, and better to have it documented than letting every new user stumble upon the same gotchas.
Overall looks good to me, some minor comments below.
Quoted 15 lines
[...]
Documentation/mm/kernel-page-tables.rst new file +13
-0+Modifying the kernel page tables +================================ + +Except for the vmalloc area, the kernel page tables are mostly static. Still, +there are cases when the permissions of existing kernel mappings have to be +updated, for instance when a module is loaded and its text becomes read-only +and executable, or when a page is temporarily removed from the direct map to +reduce its exposure. + +There are two families of functions for this, both declared in +`include/linux/set_memory.h`: + +* `set_memory_*()` change permissions of an arbitrary kernel mapping. They
Quoted 8 lines
Documentation/mm/kernel-page-tables.rst line 108
Quoted 5 lines
Documentation/mm/kernel-page-tables.rst new file +5
-0+ take a kernel virtual address and the number of pages. + +* `set_direct_map_*()` change permissions of the direct map alias of a + `struct page`. They take a `struct page` pointer and the number of + pages."direct map alias of a struct page" is rather confusing, are we talking about the mapping of struct page itself?
How about "... permissions of the direct mapping of the page frame represented by a struct page"?
Quoted 26 lines
Quoted 26 lines
Documentation/mm/kernel-page-tables.rst new file +24
-0+ +Architectures that implement `set_memory()` select `CONFIG_ARCH_HAS_SET_MEMORY` + +Architectures that implement `set_direct_map()` select +`CONFIG_ARCH_HAS_SET_DIRECT_MAP`. +Architecture specific differences +================================= + +The APIs are implemented by seven architectures and, beyond the common +semantics described above, their behaviour differs in several respects. + +Which of the APIs are implemented: + +========= ===================== ========================= +Arch `ARCH_HAS_SET_MEMORY` `ARCH_HAS_SET_DIRECT_MAP` +========= ===================== ========================= +arm yes no +arm64 yes yes +loongarch yes yes +powerpc yes no +riscv yes (MMU only) yes (MMU only) +s390 yes yes +x86 yes yes +========= ===================== =========================
Quoted 8 lines
Documentation/mm/kernel-page-tables.rst line 274
Quoted 4 lines
Documentation/mm/kernel-page-tables.rst new file +4
-0+ +Only set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() are +available everywhere, and even these are not universal: some architectures do +not implement any of them for the direct map, and the architectures that may"do not implement any of them for the direct map" feels ambiguous - I think what we really mean is that they reject direct map addresses, e.g. what arm64 does (only accept addresses to kernel VMAs)?
... some architectures restrict ranges that can be modified. For instance arm64 rejects direct map addresses.
Quoted 6 lines
Quoted 6 lines
Documentation/mm/kernel-page-tables.rst new file +6
-0+run on hardware without an execute permission bit, like x86 and s390, silently +skip the update of the executable bit there. + +set_memory_rox() has a generic implementation that calls set_memory_ro() and +set_memory_x() in turn; PowerPC, s390 and x86 override it with a single-pass +version.
Quoted 8 lines
Documentation/mm/kernel-page-tables.rst line 283
Quoted 3 lines
Documentation/mm/kernel-page-tables.rst new file +3
-0+ +Making a mapping present or not present is spelled differently: set_memory_p() +and set_memory_np() on x86 and PowerPC, set_memory_valid() on arm64 and arm.I'm not sure we should even document
set_memory_valid(). It doesn't at all behave like the other set_memory_* on amr64 (no check whatsoever, no handling of aliases) and is (fortunately) only used from arch code. I've been meaning to make its name scarier (__set_memory_valid?) for that reason.
Here it's an example of arch-private APIs, so I'd rather keep it. I'll extend the intro sentence to make it more clear.
Quoted 32 lines
Quoted 19 lines
Documentation/mm/kernel-page-tables.rst new file +19
-0+ +The direct map and the kernel image are normally mapped with the largest +possible pages, and changing the permissions of a single page inside such a +mapping requires splitting it, which not every architecture can do. + +arm +--- + +* Does not implement `set_direct_map()`. +* Provides set_memory_valid(). +* set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() accept + only vmalloc and module addresses. +* set_memory_valid() accepts any address. +* Does not update mapping aliases. + +arm64 +----- + +* Provides set_memory_valid().Documentation/mm/kernel-page-tables.rst line 312
Quoted 10 lines
Documentation/mm/kernel-page-tables.rst new file +10
-0+* Provides the memory encryption helpers, which are effective only when the + kernel runs as a confidential guest. +* set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() accept + only vmalloc and module addresses: + + - the range must fit in the VM area that contains its start + - the VM area must have `VM_ALLOC` set and `VM_ALLOW_HUGE_VMAP` clear + +* set_memory_valid() accepts any address. +* The encryption helpers accept only the direct map addresses.s/the//
Quoted 4 lines
Documentation/mm/kernel-page-tables.rst line 314
Quoted 2 lines
Documentation/mm/kernel-page-tables.rst new file +2
-0+* Propagates the read-only and the read-write changes to the direct map alias + when `rodata=full` is in effect.That is, set_memory_<perms> propagate those changes.
Ack.
Quoted 7 lines
Documentation/mm/kernel-page-tables.rst line 317
Quoted 4 lines
Documentation/mm/kernel-page-tables.rst new file +3
-0+* Splits leaf mappings before the update on the hardware that supports it. + Without such support an update that covers a leaf entry only partially fails + with a WARN()ing and `-EINVAL`.What does "partially fails" mean? The splitting may be partial, but no permission change should occur.
Yes, the split may be partial and. I'll make it clearer.
Quoted 8 lines
Quoted 8 lines
Documentation/mm/kernel-page-tables.rst new file +8
-0+* The `set_direct_map()` functions return 0 without doing anything when the + direct map cannot be modified, see can_set_direct_map(). +* Skips the TLB flush in `set_memory()` when the update only turns an invalid + mapping into a valid one. +* Does not flush TLB in `set_direct_map()`. + +LoongArch +---------
Quoted 8 lines
Documentation/mm/kernel-page-tables.rst line 331
Quoted 6 lines
Documentation/mm/kernel-page-tables.rst new file +6
-0+ +* Accepts only the addresses above the hardware window and silently returns + success for the rest, see `Direct map`_. +* Does not update mapping aliases. +* Does not split anything: a leaf entry is updated as a whole, which changes + the permissions of the entire large mapping.Ouch!
I liked more their set_direct_map part :)
Quoted 17 lines
Quoted 17 lines
Documentation/mm/kernel-page-tables.rst new file +17
-0+* Flushes the TLB in `set_direct_map()`. + +x86 +--- + +* Provides the largest set of operations on top of the common ones: + + - the cache attribute helpers: set_memory_uc(), set_memory_wc(), + set_memory_wb() + - presence control: set_memory_np() and set_memory_p() + - set_memory_4k() + - set_memory_global() and set_memory_nonglobal() + - the array variants that operate on `struct page` arrays or arrays of + virtual addresses + - memory encryption: set_memory_encrypted() and set_memory_decrypted() + +* The `set_memory()` functions accept any mapped kernel address, including the
Quoted 8 lines
Documentation/mm/kernel-page-tables.rst line 398
Quoted 6 lines
Documentation/mm/kernel-page-tables.rst new file +6
-0+ direct map, and silently succeed for the unmapped holes inside it. +* Does nothing in set_memory_x() and set_memory_nx() when the CPU has no + execute permission bit. +* Applies the change to the direct map alias and, for the kernel image, to the + high kernel mapping. The NX bit is never propagated, so that the direct map + stays non-executable.Same as above, bettermake the subject explicit (
set_memory()?).
Ack.
Quoted 1 line
- Kevin
Signature · 2 lines
Sincerely yours, Mike.