117132da7SArnd Bergmann // SPDX-License-Identifier: GPL-2.0 217132da7SArnd Bergmann // 317132da7SArnd Bergmann // originally in linux/arch/arm/plat-s3c24xx/pm.c 417132da7SArnd Bergmann // 517132da7SArnd Bergmann // Copyright (c) 2004-2008 Simtec Electronics 617132da7SArnd Bergmann // http://armlinux.simtec.co.uk 717132da7SArnd Bergmann // Ben Dooks <ben@simtec.co.uk> 817132da7SArnd Bergmann // 917132da7SArnd Bergmann // S3C Power Mangament - suspend/resume memory corruption check. 1017132da7SArnd Bergmann 1117132da7SArnd Bergmann #include <linux/kernel.h> 1217132da7SArnd Bergmann #include <linux/suspend.h> 1317132da7SArnd Bergmann #include <linux/init.h> 1417132da7SArnd Bergmann #include <linux/crc32.h> 1517132da7SArnd Bergmann #include <linux/ioport.h> 1617132da7SArnd Bergmann #include <linux/slab.h> 1717132da7SArnd Bergmann 1817132da7SArnd Bergmann #include <linux/soc/samsung/s3c-pm.h> 1917132da7SArnd Bergmann 2017132da7SArnd Bergmann #if CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE < 1 2117132da7SArnd Bergmann #error CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE must be a positive non-zero value 2217132da7SArnd Bergmann #endif 2317132da7SArnd Bergmann 2417132da7SArnd Bergmann /* suspend checking code... 2517132da7SArnd Bergmann * 2617132da7SArnd Bergmann * this next area does a set of crc checks over all the installed 2717132da7SArnd Bergmann * memory, so the system can verify if the resume was ok. 2817132da7SArnd Bergmann * 2917132da7SArnd Bergmann * CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE defines the block-size for the CRC, 3017132da7SArnd Bergmann * increasing it will mean that the area corrupted will be less easy to spot, 3117132da7SArnd Bergmann * and reducing the size will cause the CRC save area to grow 3217132da7SArnd Bergmann */ 3317132da7SArnd Bergmann 3417132da7SArnd Bergmann #define CHECK_CHUNKSIZE (CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024) 3517132da7SArnd Bergmann 3617132da7SArnd Bergmann static u32 crc_size; /* size needed for the crc block */ 3717132da7SArnd Bergmann static u32 *crcs; /* allocated over suspend/resume */ 3817132da7SArnd Bergmann 3917132da7SArnd Bergmann typedef u32 *(run_fn_t)(struct resource *ptr, u32 *arg); 4017132da7SArnd Bergmann 4117132da7SArnd Bergmann /* s3c_pm_run_res 4217132da7SArnd Bergmann * 4317132da7SArnd Bergmann * go through the given resource list, and look for system ram 4417132da7SArnd Bergmann */ 4517132da7SArnd Bergmann 4617132da7SArnd Bergmann static void s3c_pm_run_res(struct resource *ptr, run_fn_t fn, u32 *arg) 4717132da7SArnd Bergmann { 4817132da7SArnd Bergmann while (ptr != NULL) { 4917132da7SArnd Bergmann if (ptr->child != NULL) 5017132da7SArnd Bergmann s3c_pm_run_res(ptr->child, fn, arg); 5117132da7SArnd Bergmann 5217132da7SArnd Bergmann if ((ptr->flags & IORESOURCE_SYSTEM_RAM) 5317132da7SArnd Bergmann == IORESOURCE_SYSTEM_RAM) { 5417132da7SArnd Bergmann S3C_PMDBG("Found system RAM at %08lx..%08lx\n", 5517132da7SArnd Bergmann (unsigned long)ptr->start, 5617132da7SArnd Bergmann (unsigned long)ptr->end); 5717132da7SArnd Bergmann arg = (fn)(ptr, arg); 5817132da7SArnd Bergmann } 5917132da7SArnd Bergmann 6017132da7SArnd Bergmann ptr = ptr->sibling; 6117132da7SArnd Bergmann } 6217132da7SArnd Bergmann } 6317132da7SArnd Bergmann 6417132da7SArnd Bergmann static void s3c_pm_run_sysram(run_fn_t fn, u32 *arg) 6517132da7SArnd Bergmann { 6617132da7SArnd Bergmann s3c_pm_run_res(&iomem_resource, fn, arg); 6717132da7SArnd Bergmann } 6817132da7SArnd Bergmann 6917132da7SArnd Bergmann static u32 *s3c_pm_countram(struct resource *res, u32 *val) 7017132da7SArnd Bergmann { 7117132da7SArnd Bergmann u32 size = (u32)resource_size(res); 7217132da7SArnd Bergmann 7317132da7SArnd Bergmann size += CHECK_CHUNKSIZE-1; 7417132da7SArnd Bergmann size /= CHECK_CHUNKSIZE; 7517132da7SArnd Bergmann 7617132da7SArnd Bergmann S3C_PMDBG("Area %08lx..%08lx, %d blocks\n", 7717132da7SArnd Bergmann (unsigned long)res->start, (unsigned long)res->end, size); 7817132da7SArnd Bergmann 7917132da7SArnd Bergmann *val += size * sizeof(u32); 8017132da7SArnd Bergmann return val; 8117132da7SArnd Bergmann } 8217132da7SArnd Bergmann 8317132da7SArnd Bergmann /* s3c_pm_prepare_check 8417132da7SArnd Bergmann * 8517132da7SArnd Bergmann * prepare the necessary information for creating the CRCs. This 8617132da7SArnd Bergmann * must be done before the final save, as it will require memory 8717132da7SArnd Bergmann * allocating, and thus touching bits of the kernel we do not 8817132da7SArnd Bergmann * know about. 8917132da7SArnd Bergmann */ 9017132da7SArnd Bergmann 9117132da7SArnd Bergmann void s3c_pm_check_prepare(void) 9217132da7SArnd Bergmann { 9317132da7SArnd Bergmann crc_size = 0; 9417132da7SArnd Bergmann 9517132da7SArnd Bergmann s3c_pm_run_sysram(s3c_pm_countram, &crc_size); 9617132da7SArnd Bergmann 9717132da7SArnd Bergmann S3C_PMDBG("s3c_pm_prepare_check: %u checks needed\n", crc_size); 9817132da7SArnd Bergmann 9917132da7SArnd Bergmann crcs = kmalloc(crc_size+4, GFP_KERNEL); 10017132da7SArnd Bergmann if (crcs == NULL) 10117132da7SArnd Bergmann printk(KERN_ERR "Cannot allocated CRC save area\n"); 10217132da7SArnd Bergmann } 10317132da7SArnd Bergmann 10417132da7SArnd Bergmann static u32 *s3c_pm_makecheck(struct resource *res, u32 *val) 10517132da7SArnd Bergmann { 10617132da7SArnd Bergmann unsigned long addr, left; 10717132da7SArnd Bergmann 10817132da7SArnd Bergmann for (addr = res->start; addr < res->end; 10917132da7SArnd Bergmann addr += CHECK_CHUNKSIZE) { 11017132da7SArnd Bergmann left = res->end - addr; 11117132da7SArnd Bergmann 11217132da7SArnd Bergmann if (left > CHECK_CHUNKSIZE) 11317132da7SArnd Bergmann left = CHECK_CHUNKSIZE; 11417132da7SArnd Bergmann 11517132da7SArnd Bergmann *val = crc32_le(~0, phys_to_virt(addr), left); 11617132da7SArnd Bergmann val++; 11717132da7SArnd Bergmann } 11817132da7SArnd Bergmann 11917132da7SArnd Bergmann return val; 12017132da7SArnd Bergmann } 12117132da7SArnd Bergmann 12217132da7SArnd Bergmann /* s3c_pm_check_store 12317132da7SArnd Bergmann * 12417132da7SArnd Bergmann * compute the CRC values for the memory blocks before the final 12517132da7SArnd Bergmann * sleep. 12617132da7SArnd Bergmann */ 12717132da7SArnd Bergmann 12817132da7SArnd Bergmann void s3c_pm_check_store(void) 12917132da7SArnd Bergmann { 13017132da7SArnd Bergmann if (crcs != NULL) 13117132da7SArnd Bergmann s3c_pm_run_sysram(s3c_pm_makecheck, crcs); 13217132da7SArnd Bergmann } 13317132da7SArnd Bergmann 13417132da7SArnd Bergmann /* in_region 13517132da7SArnd Bergmann * 13617132da7SArnd Bergmann * return TRUE if the area defined by ptr..ptr+size contains the 13717132da7SArnd Bergmann * what..what+whatsz 13817132da7SArnd Bergmann */ 13917132da7SArnd Bergmann 14017132da7SArnd Bergmann static inline int in_region(void *ptr, int size, void *what, size_t whatsz) 14117132da7SArnd Bergmann { 14217132da7SArnd Bergmann if ((what+whatsz) < ptr) 14317132da7SArnd Bergmann return 0; 14417132da7SArnd Bergmann 14517132da7SArnd Bergmann if (what > (ptr+size)) 14617132da7SArnd Bergmann return 0; 14717132da7SArnd Bergmann 14817132da7SArnd Bergmann return 1; 14917132da7SArnd Bergmann } 15017132da7SArnd Bergmann 15117132da7SArnd Bergmann /** 15217132da7SArnd Bergmann * s3c_pm_runcheck() - helper to check a resource on restore. 15317132da7SArnd Bergmann * @res: The resource to check 154*4d05446aSLee Jones * @val: Pointer to list of CRC32 values to check. 15517132da7SArnd Bergmann * 15617132da7SArnd Bergmann * Called from the s3c_pm_check_restore() via s3c_pm_run_sysram(), this 15717132da7SArnd Bergmann * function runs the given memory resource checking it against the stored 15817132da7SArnd Bergmann * CRC to ensure that memory is restored. The function tries to skip as 15917132da7SArnd Bergmann * many of the areas used during the suspend process. 16017132da7SArnd Bergmann */ 16117132da7SArnd Bergmann static u32 *s3c_pm_runcheck(struct resource *res, u32 *val) 16217132da7SArnd Bergmann { 16317132da7SArnd Bergmann unsigned long addr; 16417132da7SArnd Bergmann unsigned long left; 16517132da7SArnd Bergmann void *stkpage; 16617132da7SArnd Bergmann void *ptr; 16717132da7SArnd Bergmann u32 calc; 16817132da7SArnd Bergmann 16917132da7SArnd Bergmann stkpage = (void *)((u32)&calc & ~PAGE_MASK); 17017132da7SArnd Bergmann 17117132da7SArnd Bergmann for (addr = res->start; addr < res->end; 17217132da7SArnd Bergmann addr += CHECK_CHUNKSIZE) { 17317132da7SArnd Bergmann left = res->end - addr; 17417132da7SArnd Bergmann 17517132da7SArnd Bergmann if (left > CHECK_CHUNKSIZE) 17617132da7SArnd Bergmann left = CHECK_CHUNKSIZE; 17717132da7SArnd Bergmann 17817132da7SArnd Bergmann ptr = phys_to_virt(addr); 17917132da7SArnd Bergmann 18017132da7SArnd Bergmann if (in_region(ptr, left, stkpage, 4096)) { 18117132da7SArnd Bergmann S3C_PMDBG("skipping %08lx, has stack in\n", addr); 18217132da7SArnd Bergmann goto skip_check; 18317132da7SArnd Bergmann } 18417132da7SArnd Bergmann 18517132da7SArnd Bergmann if (in_region(ptr, left, crcs, crc_size)) { 18617132da7SArnd Bergmann S3C_PMDBG("skipping %08lx, has crc block in\n", addr); 18717132da7SArnd Bergmann goto skip_check; 18817132da7SArnd Bergmann } 18917132da7SArnd Bergmann 19017132da7SArnd Bergmann /* calculate and check the checksum */ 19117132da7SArnd Bergmann 19217132da7SArnd Bergmann calc = crc32_le(~0, ptr, left); 19317132da7SArnd Bergmann if (calc != *val) { 19417132da7SArnd Bergmann printk(KERN_ERR "Restore CRC error at " 19517132da7SArnd Bergmann "%08lx (%08x vs %08x)\n", addr, calc, *val); 19617132da7SArnd Bergmann 19717132da7SArnd Bergmann S3C_PMDBG("Restore CRC error at %08lx (%08x vs %08x)\n", 19817132da7SArnd Bergmann addr, calc, *val); 19917132da7SArnd Bergmann } 20017132da7SArnd Bergmann 20117132da7SArnd Bergmann skip_check: 20217132da7SArnd Bergmann val++; 20317132da7SArnd Bergmann } 20417132da7SArnd Bergmann 20517132da7SArnd Bergmann return val; 20617132da7SArnd Bergmann } 20717132da7SArnd Bergmann 20817132da7SArnd Bergmann /** 20917132da7SArnd Bergmann * s3c_pm_check_restore() - memory check called on resume 21017132da7SArnd Bergmann * 21117132da7SArnd Bergmann * check the CRCs after the restore event and free the memory used 21217132da7SArnd Bergmann * to hold them 21317132da7SArnd Bergmann */ 21417132da7SArnd Bergmann void s3c_pm_check_restore(void) 21517132da7SArnd Bergmann { 21617132da7SArnd Bergmann if (crcs != NULL) 21717132da7SArnd Bergmann s3c_pm_run_sysram(s3c_pm_runcheck, crcs); 21817132da7SArnd Bergmann } 21917132da7SArnd Bergmann 22017132da7SArnd Bergmann /** 22117132da7SArnd Bergmann * s3c_pm_check_cleanup() - free memory resources 22217132da7SArnd Bergmann * 22317132da7SArnd Bergmann * Free the resources that where allocated by the suspend 22417132da7SArnd Bergmann * memory check code. We do this separately from the 22517132da7SArnd Bergmann * s3c_pm_check_restore() function as we cannot call any 22617132da7SArnd Bergmann * functions that might sleep during that resume. 22717132da7SArnd Bergmann */ 22817132da7SArnd Bergmann void s3c_pm_check_cleanup(void) 22917132da7SArnd Bergmann { 23017132da7SArnd Bergmann kfree(crcs); 23117132da7SArnd Bergmann crcs = NULL; 23217132da7SArnd Bergmann } 23317132da7SArnd Bergmann 234