fdmemory: add flag to avoid close of the fd
[platform/upstream/gstreamer.git] / gst-libs / gst / allocators / gstfdmemory.c
1 /* GStreamer fd backed memory
2  * Copyright (C) 2013 Linaro SA
3  * Author: Benjamin Gaignard <benjamin.gaignard@linaro.org> for Linaro.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for mordetails.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:gstfdmemory
23  * @short_description: Memory wrapper for fd backed memory
24  * @see_also: #GstMemory
25  *
26  * Since: 1.4
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include "gstfdmemory.h"
34
35 #ifdef HAVE_MMAP
36 #include <sys/mman.h>
37 #include <unistd.h>
38 #endif
39
40 GST_DEBUG_CATEGORY_STATIC (gst_fdmemory_debug);
41 #define GST_CAT_DEFAULT gst_fdmemory_debug
42
43 typedef struct
44 {
45   GstMemory mem;
46
47   GstFdMemoryFlags flags;
48   gint fd;
49   gpointer data;
50   gint mmapping_flags;
51   gint mmap_count;
52   GMutex lock;
53 } GstFdMemory;
54
55 static void
56 gst_fd_mem_free (GstAllocator * allocator, GstMemory * gmem)
57 {
58 #ifdef HAVE_MMAP
59   GstFdMemory *mem = (GstFdMemory *) gmem;
60
61   if (mem->data) {
62     if (!(mem->flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED))
63       g_warning (G_STRLOC ":%s: Freeing memory %p still mapped", G_STRFUNC,
64           mem);
65
66     munmap ((void *) mem->data, gmem->maxsize);
67   }
68   if (mem->fd >= 0 && gmem->parent == NULL
69       && !(mem->flags & GST_FD_MEMORY_FLAG_DONT_CLOSE))
70     close (mem->fd);
71   g_mutex_clear (&mem->lock);
72   g_slice_free (GstFdMemory, mem);
73   GST_DEBUG ("%p: freed", mem);
74 #endif
75 }
76
77 static gpointer
78 gst_fd_mem_map (GstMemory * gmem, gsize maxsize, GstMapFlags flags)
79 {
80 #ifdef HAVE_MMAP
81   GstFdMemory *mem = (GstFdMemory *) gmem;
82   gint prot;
83   gpointer ret = NULL;
84
85   if (gmem->parent)
86     return gst_fd_mem_map (gmem->parent, maxsize, flags);
87
88   prot = flags & GST_MAP_READ ? PROT_READ : 0;
89   prot |= flags & GST_MAP_WRITE ? PROT_WRITE : 0;
90
91   g_mutex_lock (&mem->lock);
92   /* do not mmap twice the buffer */
93   if (mem->data) {
94     /* only return address if mapping flags are a subset
95      * of the previous flags */
96     if ((mem->mmapping_flags & prot) == prot) {
97       ret = mem->data;
98       mem->mmap_count++;
99     }
100
101     goto out;
102   }
103
104   if (mem->fd != -1) {
105     gint flags;
106
107     flags =
108         (mem->flags & GST_FD_MEMORY_FLAG_MAP_PRIVATE) ? MAP_PRIVATE :
109         MAP_SHARED;
110
111     mem->data = mmap (0, gmem->maxsize, prot, flags, mem->fd, 0);
112     if (mem->data == MAP_FAILED) {
113       mem->data = NULL;
114       GST_ERROR ("%p: fd %d: mmap failed: %s", mem, mem->fd,
115           g_strerror (errno));
116       goto out;
117     }
118   }
119
120   GST_DEBUG ("%p: fd %d: mapped %p", mem, mem->fd, mem->data);
121
122   if (mem->data) {
123     mem->mmapping_flags = prot;
124     mem->mmap_count++;
125     ret = mem->data;
126   }
127
128 out:
129   g_mutex_unlock (&mem->lock);
130   return ret;
131 #else /* !HAVE_MMAP */
132   return FALSE;
133 #endif
134 }
135
136 static void
137 gst_fd_mem_unmap (GstMemory * gmem)
138 {
139 #ifdef HAVE_MMAP
140   GstFdMemory *mem = (GstFdMemory *) gmem;
141
142   if (gmem->parent)
143     return gst_fd_mem_unmap (gmem->parent);
144
145   if (mem->flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED)
146     return;
147
148   g_mutex_lock (&mem->lock);
149   if (mem->data && !(--mem->mmap_count)) {
150     munmap ((void *) mem->data, gmem->maxsize);
151     mem->data = NULL;
152     mem->mmapping_flags = 0;
153     GST_DEBUG ("%p: fd %d unmapped", mem, mem->fd);
154   }
155   g_mutex_unlock (&mem->lock);
156 #endif
157 }
158
159 static GstMemory *
160 gst_fd_mem_share (GstMemory * gmem, gssize offset, gssize size)
161 {
162 #ifdef HAVE_MMAP
163   GstFdMemory *mem = (GstFdMemory *) gmem;
164   GstFdMemory *sub;
165   GstMemory *parent;
166
167   GST_DEBUG ("%p: share %" G_GSSIZE_FORMAT " %" G_GSIZE_FORMAT, mem, offset,
168       size);
169
170   /* find the real parent */
171   if ((parent = mem->mem.parent) == NULL)
172     parent = (GstMemory *) mem;
173
174   if (size == -1)
175     size = gmem->maxsize - offset;
176
177   sub = g_slice_new0 (GstFdMemory);
178   /* the shared memory is always readonly */
179   gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) |
180       GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->mem.allocator, parent,
181       mem->mem.maxsize, mem->mem.align, mem->mem.offset + offset, size);
182
183   sub->fd = mem->fd;
184   g_mutex_init (&sub->lock);
185
186   return GST_MEMORY_CAST (sub);
187 #else /* !HAVE_MMAP */
188   return NULL;
189 #endif
190 }
191
192 G_DEFINE_TYPE (GstFdAllocator, gst_fd_allocator, GST_TYPE_ALLOCATOR);
193
194 static void
195 gst_fd_allocator_class_init (GstFdAllocatorClass * klass)
196 {
197   GstAllocatorClass *allocator_class;
198
199   allocator_class = (GstAllocatorClass *) klass;
200
201   allocator_class->alloc = NULL;
202   allocator_class->free = gst_fd_mem_free;
203
204   GST_DEBUG_CATEGORY_INIT (gst_fdmemory_debug, "fdmemory", 0,
205       "GstFdMemory and GstFdAllocator");
206 }
207
208 static void
209 gst_fd_allocator_init (GstFdAllocator * allocator)
210 {
211   GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
212
213   alloc->mem_type = GST_ALLOCATOR_FD;
214
215   alloc->mem_map = gst_fd_mem_map;
216   alloc->mem_unmap = gst_fd_mem_unmap;
217   alloc->mem_share = gst_fd_mem_share;
218
219   GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
220 }
221
222 /**
223  * gst_fd_allocator_new:
224  *
225  * Return a new fd allocator.
226  *
227  * Returns: (transfer full): a new fd allocator, or NULL if the allocator
228  *    isn't available. Use gst_object_unref() to release the allocator after
229  *    usage
230  *
231  * Since: 1.6
232  */
233 GstAllocator *
234 gst_fd_allocator_new (void)
235 {
236   return g_object_new (GST_TYPE_FD_ALLOCATOR, NULL);
237 }
238
239 /**
240  * gst_fd_allocator_alloc:
241  * @allocator: allocator to be used for this memory
242  * @fd: file descriptor
243  * @size: memory size
244  * @flags: extra #GstFdMemoryFlags
245  *
246  * Return a %GstMemory that wraps a generic file descriptor.
247  *
248  * Returns: (transfer full): a GstMemory based on @allocator.
249  * When the buffer will be released the allocator will close the @fd unless
250  * the %GST_FD_MEMORY_FLAG_DONT_CLOSE flag is specified.
251  * The memory is only mmapped on gst_buffer_mmap() request.
252  *
253  * Since: 1.6
254  */
255 GstMemory *
256 gst_fd_allocator_alloc (GstAllocator * allocator, gint fd, gsize size,
257     GstFdMemoryFlags flags)
258 {
259 #ifdef HAVE_MMAP
260   GstFdMemory *mem;
261
262   g_return_val_if_fail (GST_IS_FD_ALLOCATOR (allocator), NULL);
263
264   mem = g_slice_new0 (GstFdMemory);
265   gst_memory_init (GST_MEMORY_CAST (mem), 0, GST_ALLOCATOR_CAST (allocator),
266       NULL, size, 0, 0, size);
267
268   mem->flags = flags;
269   mem->fd = fd;
270   g_mutex_init (&mem->lock);
271
272   GST_DEBUG ("%p: fd: %d size %" G_GSIZE_FORMAT, mem, mem->fd,
273       mem->mem.maxsize);
274
275   return (GstMemory *) mem;
276 #else /* !HAVE_MMAP */
277   return NULL;
278 #endif
279 }
280
281 /**
282  * gst_is_fd_memory:
283  * @mem: #GstMemory
284  *
285  * Check if @mem is memory backed by an fd
286  *
287  * Returns: %TRUE when @mem has an fd that can be retrieved with
288  * gst_fd_memory_get_fd().
289  *
290  * Since: 1.6
291  */
292 gboolean
293 gst_is_fd_memory (GstMemory * mem)
294 {
295   g_return_val_if_fail (mem != NULL, FALSE);
296
297   return GST_IS_FD_ALLOCATOR (mem->allocator);
298 }
299
300 /**
301  * gst_fd_memory_get_fd:
302  * @mem: #GstMemory
303  *
304  * Get the fd from @mem. Call gst_is_fd_memory() to check if @mem has
305  * an fd.
306  *
307  * Returns: the fd of @mem or -1 when there is no fd on @mem
308  *
309  * Since: 1.6
310  */
311 gint
312 gst_fd_memory_get_fd (GstMemory * mem)
313 {
314   g_return_val_if_fail (mem != NULL, -1);
315   g_return_val_if_fail (GST_IS_FD_ALLOCATOR (mem->allocator), -1);
316
317   return ((GstFdMemory *) mem)->fd;
318 }