summaryrefslogtreecommitdiff
path: root/lib/shm_signal.c
blob: 8d3e9b418a274e6652c1cbd9169336d46bdb7cb6 (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
/*
 * Copyright 2009 Novell.  All Rights Reserved.
 *
 * See include/linux/shm_signal.h for documentation
 *
 * Author:
 *      Gregory Haskins <ghaskins@novell.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/shm_signal.h>

MODULE_AUTHOR("Gregory Haskins");
MODULE_LICENSE("GPL");
MODULE_VERSION("1");

int shm_signal_enable(struct shm_signal *s, int flags)
{
	struct shm_signal_irq *irq = &s->desc->irq[s->locale];
	unsigned long iflags;

	spin_lock_irqsave(&s->lock, iflags);

	irq->enabled = 1;
	wmb();

	if ((irq->dirty || irq->pending)
	    && !test_bit(shm_signal_in_wakeup, &s->flags)) {
		rmb();
		tasklet_schedule(&s->deferred_notify);
	}

	spin_unlock_irqrestore(&s->lock, iflags);

	return 0;
}
EXPORT_SYMBOL_GPL(shm_signal_enable);

int shm_signal_disable(struct shm_signal *s, int flags)
{
	struct shm_signal_irq *irq = &s->desc->irq[s->locale];

	irq->enabled = 0;
	wmb();

	return 0;
}
EXPORT_SYMBOL_GPL(shm_signal_disable);

/*
 * signaling protocol:
 *
 * each side of the shm_signal has an "irq" structure with the following
 * fields:
 *
 *    - enabled: controlled by shm_signal_enable/disable() to mask/unmask
 *               the notification locally
 *    - dirty:   indicates if the shared-memory is dirty or clean.  This
 *               is updated regardless of the enabled/pending state so that
 *               the state is always accurately tracked.
 *    - pending: indicates if a signal is pending to the remote locale.
 *               This allows us to determine if a remote-notification is
 *               already in flight to optimize spurious notifications away.
 */
int shm_signal_inject(struct shm_signal *s, int flags)
{
	/* Load the irq structure from the other locale */
	struct shm_signal_irq *irq = &s->desc->irq[!s->locale];

	/*
	 * We always mark the remote side as dirty regardless of whether
	 * they need to be notified.
	 */
	irq->dirty = 1;
	wmb();   /* dirty must be visible before we test the pending state */

	if (irq->enabled && !irq->pending) {
		rmb();

		/*
		 * If the remote side has enabled notifications, and we do
		 * not see a notification pending, we must inject a new one.
		 */
		irq->pending = 1;
		wmb(); /* make it visible before we do the injection */

		s->ops->inject(s);
	}

	return 0;
}
EXPORT_SYMBOL_GPL(shm_signal_inject);

void _shm_signal_wakeup(struct shm_signal *s)
{
	struct shm_signal_irq *irq = &s->desc->irq[s->locale];
	int dirty;
	unsigned long flags;

	spin_lock_irqsave(&s->lock, flags);

	__set_bit(shm_signal_in_wakeup, &s->flags);

	/*
	 * The outer loop protects against race conditions between
	 * irq->dirty and irq->pending updates
	 */
	while (irq->enabled && (irq->dirty || irq->pending)) {

		/*
		 * Run until we completely exhaust irq->dirty (it may
		 * be re-dirtied by the remote side while we are in the
		 * callback).  We let "pending" remain untouched until we have
		 * processed them all so that the remote side knows we do not
		 * need a new notification (yet).
		 */
		do {
			irq->dirty = 0;
			/* the unlock is an implicit wmb() for dirty = 0 */
			spin_unlock_irqrestore(&s->lock, flags);

			if (s->notifier)
				s->notifier->signal(s->notifier);

			spin_lock_irqsave(&s->lock, flags);
			dirty = irq->dirty;
			rmb();

		} while (irq->enabled && dirty);

		barrier();

		/*
		 * We can finally acknowledge the notification by clearing
		 * "pending" after all of the dirty memory has been processed
		 * Races against this clearing are handled by the outer loop.
		 * Subsequent iterations of this loop will execute with
		 * pending=0 potentially leading to future spurious
		 * notifications, but this is an acceptable tradeoff as this
		 * will be rare and harmless.
		 */
		irq->pending = 0;
		wmb();

	}

	__clear_bit(shm_signal_in_wakeup, &s->flags);
	spin_unlock_irqrestore(&s->lock, flags);

}
EXPORT_SYMBOL_GPL(_shm_signal_wakeup);

void _shm_signal_release(struct kref *kref)
{
	struct shm_signal *s = container_of(kref, struct shm_signal, kref);

	s->ops->release(s);
}
EXPORT_SYMBOL_GPL(_shm_signal_release);

static void
deferred_notify(unsigned long data)
{
	struct shm_signal *s = (struct shm_signal *)data;

	_shm_signal_wakeup(s);
}

void shm_signal_init(struct shm_signal *s, enum shm_signal_locality locale,
		     struct shm_signal_ops *ops, struct shm_signal_desc *desc)
{
	memset(s, 0, sizeof(*s));
	kref_init(&s->kref);
	spin_lock_init(&s->lock);
	tasklet_init(&s->deferred_notify,
		     deferred_notify,
		     (unsigned long)s);
	s->locale   = locale;
	s->ops      = ops;
	s->desc     = desc;
}
EXPORT_SYMBOL_GPL(shm_signal_init);