summaryrefslogtreecommitdiff
path: root/kernel/power/tuxonice_copy_before_write.c
blob: eb627915e30332a3aafb6683809e5ff638223822 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/*
 * kernel/power/tuxonice_copy_before_write.c
 *
 * Copyright (C) 2015 Nigel Cunningham (nigel at nigelcunningham com au)
 *
 * This file is released under the GPLv2.
 *
 * Routines (apart from the fault handling code) to deal with allocating memory
 * for copying pages before they are modified, restoring the contents and getting
 * the contents written to disk.
 */

#include <linux/percpu-defs.h>
#include <linux/sched.h>
#include <linux/tuxonice.h>
#include "tuxonice_alloc.h"
#include "tuxonice_modules.h"
#include "tuxonice_sysfs.h"
#include "tuxonice.h"

DEFINE_PER_CPU(struct toi_cbw_state, toi_cbw_states);
#define CBWS_PER_PAGE (PAGE_SIZE / sizeof(struct toi_cbw))
#define toi_cbw_pool_size 100

static void _toi_free_cbw_data(struct toi_cbw_state *state)
{
    struct toi_cbw *page_ptr, *ptr, *next;

    page_ptr = ptr = state->first;

    while(ptr) {
        next = ptr->next;

        if (ptr->virt) {
            toi__free_page(40, virt_to_page(ptr->virt));
        }
        if ((((unsigned long) ptr) & PAGE_MASK) != (unsigned long) page_ptr) {
            /* Must be on a new page - free the previous one. */
            toi__free_page(40, virt_to_page(page_ptr));
            page_ptr = ptr;
        }
        ptr = next;
    }

    if (page_ptr) {
        toi__free_page(40, virt_to_page(page_ptr));
    }

    state->first = state->next = state->last = NULL;
    state->size = 0;
}

void toi_free_cbw_data(void)
{
    int i;

    for_each_online_cpu(i) {
        struct toi_cbw_state *state = &per_cpu(toi_cbw_states, i);

        if (!state->first)
            continue;

        state->enabled = 0;

        while (state->active) {
            schedule();
        }

        _toi_free_cbw_data(state);
    }
}

static int _toi_allocate_cbw_data(struct toi_cbw_state *state)
{
    while(state->size < toi_cbw_pool_size) {
        int i;
        struct toi_cbw *ptr;

        ptr = (struct toi_cbw *) toi_get_zeroed_page(40, GFP_KERNEL);

        if (!ptr) {
            return -ENOMEM;
        }

        if (!state->first) {
            state->first = state->next = state->last = ptr;
        }

        for (i = 0; i < CBWS_PER_PAGE; i++) {
            struct toi_cbw *cbw = &ptr[i];

            cbw->virt = (char *) toi_get_zeroed_page(40, GFP_KERNEL);
            if (!cbw->virt) {
                state->size += i;
                printk("Out of memory allocating CBW pages.\n");
                return -ENOMEM;
            }

            if (cbw == state->first)
                continue;

            state->last->next = cbw;
            state->last = cbw;
        }

        state->size += CBWS_PER_PAGE;
    }

    state->enabled = 1;

    return 0;
}


int toi_allocate_cbw_data(void)
{
    int i, result;

    for_each_online_cpu(i) {
        struct toi_cbw_state *state = &per_cpu(toi_cbw_states, i);

        result = _toi_allocate_cbw_data(state);

        if (result)
            return result;
    }

    return 0;
}

void toi_cbw_restore(void)
{
    if (!toi_keeping_image)
        return;

}

void toi_cbw_write(void)
{
    if (!toi_keeping_image)
        return;

}

/**
 * toi_cbw_test_read - Test copy before write on one page
 *
 * Allocate copy before write buffers, then make one page only copy-before-write
 * and attempt to write to it. We should then be able to retrieve the original
 * version from the cbw buffer and the modified version from the page itself.
 */
static int toi_cbw_test_read(const char *buffer, int count)
{
    unsigned long virt = toi_get_zeroed_page(40, GFP_KERNEL);
    char *original = "Original contents";
    char *modified = "Modified material";
    struct page *page = virt_to_page(virt);
    int i, len = 0, found = 0, pfn = page_to_pfn(page);

    if (!page) {
        printk("toi_cbw_test_read: Unable to allocate a page for testing.\n");
        return -ENOMEM;
    }

    memcpy((char *) virt, original, strlen(original));

    if (toi_allocate_cbw_data()) {
        printk("toi_cbw_test_read: Unable to allocate cbw data.\n");
        return -ENOMEM;
    }

    toi_reset_dirtiness_one(pfn, 0);

    SetPageTOI_CBW(page);

    memcpy((char *) virt, modified, strlen(modified));

    if (strncmp((char *) virt, modified, strlen(modified))) {
        len += sprintf((char *) buffer + len, "Failed to write to page after protecting it.\n");
    }

    for_each_online_cpu(i) {
        struct toi_cbw_state *state = &per_cpu(toi_cbw_states, i);
        struct toi_cbw *ptr = state->first, *last_ptr = ptr;

        if (!found) {
            while (ptr) {
                if (ptr->pfn == pfn) {
                    found = 1;
                    if (strncmp(ptr->virt, original, strlen(original))) {
                        len += sprintf((char *) buffer + len, "Contents of original buffer are not original.\n");
                    } else {
                        len += sprintf((char *) buffer + len, "Test passed. Buffer changed and original contents preserved.\n");
                    }
                    break;
                }

                last_ptr = ptr;
                ptr = ptr->next;
            }
        }

        if (!last_ptr)
            len += sprintf((char *) buffer + len, "All available CBW buffers on cpu %d used.\n", i);
    }

    if (!found)
        len += sprintf((char *) buffer + len, "Copy before write buffer not found.\n");

    toi_free_cbw_data();

    return len;
}

/*
 * This array contains entries that are automatically registered at
 * boot. Modules and the console code register their own entries separately.
 */
static struct toi_sysfs_data sysfs_params[] = {
        SYSFS_CUSTOM("test", SYSFS_RW, toi_cbw_test_read,
                        NULL, SYSFS_NEEDS_SM_FOR_READ, NULL),
};

static struct toi_module_ops toi_cbw_ops = {
        .type                                        = MISC_HIDDEN_MODULE,
        .name                                        = "copy_before_write debugging",
        .directory                                = "cbw",
        .module                                        = THIS_MODULE,
        .early                                        = 1,

        .sysfs_data                = sysfs_params,
        .num_sysfs_entries        = sizeof(sysfs_params) /
                sizeof(struct toi_sysfs_data),
};

int toi_cbw_init(void)
{
        int result = toi_register_module(&toi_cbw_ops);
        return result;
}