* It includes the compatibility string, the liveupdate-number, and pointers
* to sessions and FLBs.
*
- * - struct luo_session_header_ser:
- * Header for the session array. Contains the total page count of the
- * preserved memory block and the number of `struct luo_session_ser`
- * entries that follow.
- *
* - struct luo_session_ser:
* Metadata for a single session, including its name and a physical pointer
* to another preserved memory block containing an array of
#define _LINUX_KHO_ABI_LUO_H
#include <linux/align.h>
+#include <linux/kho/abi/block.h>
#include <uapi/linux/liveupdate.h>
/*
* The LUO state is registered under this KHO entry name.
*/
#define LUO_KHO_ENTRY_NAME "LUO"
-#define LUO_ABI_COMPATIBLE "luo-v3"
+#define LUO_ABI_COMPATIBLE "luo-v4"
#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
/**
u64 count;
} __packed;
-/**
- * struct luo_session_header_ser - Header for the serialized session data block.
- * @count: The number of `struct luo_session_ser` entries that immediately
- * follow this header in the memory block.
- *
- * This structure is located at the beginning of a contiguous block of
- * physical memory preserved across the kexec. It provides the necessary
- * metadata to interpret the array of session entries that follow.
- *
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
- */
-struct luo_session_header_ser {
- u64 count;
-} __packed;
-
/**
* struct luo_session_ser - Represents the serialized metadata for a LUO session.
* @name: The unique name of the session, provided by the userspace at
* ioctls on /dev/liveupdate.
*
* - Serialization: Session metadata is preserved using the KHO framework. When
- * a live update is triggered via kexec, an array of `struct luo_session_ser`
- * is populated and placed in a preserved memory region. The physical address
- * of this array is stored in the centralized `struct luo_ser` structure.
+ * a live update is triggered via kexec, session metadata is serialized into
+ * a chain of linked-blocks and placed in a preserved memory region. The
+ * physical address of the first block header is stored in the centralized
+ * `struct luo_ser` structure.
*
* Session Lifecycle:
*
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
+#include <linux/kho_block.h>
#include <linux/kho/abi/luo.h>
#include <linux/list.h>
#include <linux/liveupdate.h>
#include <uapi/linux/liveupdate.h>
#include "luo_internal.h"
-/* 16 4K pages, give space for 744 sessions */
-#define LUO_SESSION_PGCNT 16ul
-#define LUO_SESSION_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \
- sizeof(struct luo_session_header_ser)) / \
- sizeof(struct luo_session_ser))
-
static DECLARE_RWSEM(luo_session_serialize_rwsem);
-
/**
* struct luo_session_header - Header struct for managing LUO sessions.
* @count: The number of sessions currently tracked in the @list.
* @list: The head of the linked list of `struct luo_session` instances.
* @rwsem: A read-write semaphore providing synchronized access to the
* session list and other fields in this structure.
- * @header_ser: The header data of serialization array.
- * @ser: The serialized session data (an array of
- * `struct luo_session_ser`).
+ * @block_set: The set of serialization blocks.
* @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
* @active: Set to true when first initialized. If previous kernel did not
* send session data, active stays false for incoming.
long count;
struct list_head list;
struct rw_semaphore rwsem;
- struct luo_session_header_ser *header_ser;
- struct luo_session_ser *ser;
+ struct kho_block_set block_set;
u64 *sessions_pa;
bool active;
};
.incoming = {
.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+ .block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
+ sizeof(struct luo_session_ser)),
},
.outgoing = {
.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+ .block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
+ sizeof(struct luo_session_ser)),
},
};
kfree(session);
}
-static int luo_session_grow_ser(struct luo_session_header *sh)
-{
- struct luo_session_header_ser *header_ser;
-
- if (sh->count == LUO_SESSION_MAX)
- return -ENOMEM;
-
- if (sh->header_ser)
- return 0;
-
- header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT);
- if (IS_ERR(header_ser))
- return PTR_ERR(header_ser);
-
- sh->header_ser = header_ser;
- sh->ser = (void *)(header_ser + 1);
- return 0;
-}
-
static int luo_session_insert(struct luo_session_header *sh,
struct luo_session *session)
{
* for new session.
*/
if (sh == &luo_session_global.outgoing) {
- err = luo_session_grow_ser(sh);
+ err = kho_block_set_grow(&sh->block_set, sh->count + 1);
if (err)
return err;
}
guard(rwsem_write)(&sh->rwsem);
list_del(&session->list);
sh->count--;
+ if (sh == &luo_session_global.outgoing)
+ kho_block_set_shrink(&sh->block_set, sh->count);
}
static int luo_session_finish_one(struct luo_session *session)
int __init luo_session_setup_incoming(u64 sessions_pa)
{
- struct luo_session_header_ser *header_ser;
+ struct luo_session_header *sh = &luo_session_global.incoming;
+ int err;
- if (sessions_pa) {
- header_ser = phys_to_virt(sessions_pa);
- luo_session_global.incoming.header_ser = header_ser;
- luo_session_global.incoming.ser = (void *)(header_ser + 1);
- luo_session_global.incoming.active = true;
- }
+ if (!sessions_pa)
+ return 0;
+ err = kho_block_set_restore(&sh->block_set, sessions_pa);
+ if (err)
+ return err;
+
+ sh->active = true;
return 0;
}
{
struct luo_session_header *sh = &luo_session_global.incoming;
static bool is_deserialized;
+ struct luo_session_ser *ser;
+ struct kho_block_set_it it;
static int saved_err;
int err;
* userspace to detect the failure and trigger a reboot, which will
* reliably reset devices and reclaim memory.
*/
- for (int i = 0; i < sh->header_ser->count; i++) {
- err = luo_session_deserialize_one(sh, &sh->ser[i]);
+ kho_block_set_it_init(&it, &sh->block_set);
+ while ((ser = kho_block_set_it_read_entry(&it))) {
+ err = luo_session_deserialize_one(sh, ser);
if (err)
goto save_err;
}
- kho_restore_free(sh->header_ser);
- sh->header_ser = NULL;
- sh->ser = NULL;
+ kho_block_set_destroy(&sh->block_set);
return 0;
+
save_err:
+ kho_block_set_destroy(&sh->block_set);
saved_err = err;
return err;
}
{
struct luo_session_header *sh = &luo_session_global.outgoing;
struct luo_session *session;
- int i = 0;
+ struct kho_block_set_it it;
int err;
down_write(&luo_session_serialize_rwsem);
down_write(&sh->rwsem);
*sh->sessions_pa = 0;
+ kho_block_set_it_init(&it, &sh->block_set);
+
list_for_each_entry(session, &sh->list, list) {
- err = luo_session_freeze_one(session, &sh->ser[i]);
- if (err)
+ struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
+
+ /* This should not fail normally as blocks were pre-allocated */
+ if (WARN_ON_ONCE(!ser)) {
+ err = -ENOSPC;
goto err_undo;
+ }
- strscpy(sh->ser[i].name, session->name,
- sizeof(sh->ser[i].name));
- i++;
- }
+ err = luo_session_freeze_one(session, ser);
+ if (err) {
+ kho_block_set_it_prev(&it);
+ goto err_undo;
+ }
- if (sh->header_ser && sh->count > 0) {
- sh->header_ser->count = sh->count;
- *sh->sessions_pa = virt_to_phys(sh->header_ser);
+ strscpy(ser->name, session->name, sizeof(ser->name));
}
+
+ if (sh->count > 0)
+ *sh->sessions_pa = kho_block_set_head_pa(&sh->block_set);
up_write(&sh->rwsem);
return 0;
err_undo:
list_for_each_entry_continue_reverse(session, &sh->list, list) {
- i--;
- luo_session_unfreeze_one(session, &sh->ser[i]);
- memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
+ struct luo_session_ser *ser = kho_block_set_it_prev(&it);
+
+ luo_session_unfreeze_one(session, ser);
+ memset(ser->name, 0, sizeof(ser->name));
}
up_write(&sh->rwsem);
up_write(&luo_session_serialize_rwsem);