Commit c0ee755f authored by Dave Airlie's avatar Dave Airlie

Merge tag 'topic/core-stuff-2014-08-15' of git://anongit.freedesktop.org/drm-intel into drm-next

So small drm stuff all over for 3.18. Biggest one is the cmdline parsing
from Chris with a few fixes from me to make it work for stupid kernel
configs.

Plus the atomic prep series.

Tested for more than a week in -nightly and Ville/Imre indeed discovered
some fun which is now fixed (and i915 vblank patches postponed since the
fixups need this branch plus drm-intel-next merged together).

* tag 'topic/core-stuff-2014-08-15' of git://anongit.freedesktop.org/drm-intel:
  drm: Use the type of the array element when reallocating
  drm: Don't return 0 for a value used as a denominator
  drm: Docbook fixes
  drm/irq: Implement a generic vblank_wait function
  drm: Add a plane->reset hook
  drm: trylock modest locking for fbdev panics
  drm: Move ->old_fb from crtc to plane
  drm: Handle legacy per-crtc locking with full acquire ctx
  drm: Move modeset_lock_all helpers to drm_modeset_lock.[hc]
  drm: Add drm_plane/connector_index
  drm: idiot-proof vblank
  drm: Warn when leaking flip events on close
  drm: Perform cmdline mode parsing during connector initialisation
  video/fbdev: Always built-in video= cmdline parsing
  drm: Don't grab an fb reference for the idr
parents 52addcf9 14f476fa
......@@ -8,6 +8,7 @@ menuconfig DRM
tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)"
depends on (AGP || AGP=n) && !EMULATED_CMPXCHG && MMU && HAS_DMA
select HDMI
select FB_CMDLINE
select I2C
select I2C_ALGOBIT
select DMA_SHARED_BUFFER
......
......@@ -45,101 +45,6 @@ static struct drm_framebuffer *add_framebuffer_internal(struct drm_device *dev,
struct drm_mode_fb_cmd2 *r,
struct drm_file *file_priv);
/**
* drm_modeset_lock_all - take all modeset locks
* @dev: drm device
*
* This function takes all modeset locks, suitable where a more fine-grained
* scheme isn't (yet) implemented. Locks must be dropped with
* drm_modeset_unlock_all.
*/
void drm_modeset_lock_all(struct drm_device *dev)
{
struct drm_mode_config *config = &dev->mode_config;
struct drm_modeset_acquire_ctx *ctx;
int ret;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (WARN_ON(!ctx))
return;
mutex_lock(&config->mutex);
drm_modeset_acquire_init(ctx, 0);
retry:
ret = drm_modeset_lock(&config->connection_mutex, ctx);
if (ret)
goto fail;
ret = drm_modeset_lock_all_crtcs(dev, ctx);
if (ret)
goto fail;
WARN_ON(config->acquire_ctx);
/* now we hold the locks, so now that it is safe, stash the
* ctx for drm_modeset_unlock_all():
*/
config->acquire_ctx = ctx;
drm_warn_on_modeset_not_all_locked(dev);
return;
fail:
if (ret == -EDEADLK) {
drm_modeset_backoff(ctx);
goto retry;
}
}
EXPORT_SYMBOL(drm_modeset_lock_all);
/**
* drm_modeset_unlock_all - drop all modeset locks
* @dev: device
*
* This function drop all modeset locks taken by drm_modeset_lock_all.
*/
void drm_modeset_unlock_all(struct drm_device *dev)
{
struct drm_mode_config *config = &dev->mode_config;
struct drm_modeset_acquire_ctx *ctx = config->acquire_ctx;
if (WARN_ON(!ctx))
return;
config->acquire_ctx = NULL;
drm_modeset_drop_locks(ctx);
drm_modeset_acquire_fini(ctx);
kfree(ctx);
mutex_unlock(&dev->mode_config.mutex);
}
EXPORT_SYMBOL(drm_modeset_unlock_all);
/**
* drm_warn_on_modeset_not_all_locked - check that all modeset locks are locked
* @dev: device
*
* Useful as a debug assert.
*/
void drm_warn_on_modeset_not_all_locked(struct drm_device *dev)
{
struct drm_crtc *crtc;
/* Locking is currently fubar in the panic handler. */
if (oops_in_progress)
return;
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex));
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
}
EXPORT_SYMBOL(drm_warn_on_modeset_not_all_locked);
/* Avoid boilerplate. I'm tired of typing. */
#define DRM_ENUM_NAME_FN(fnname, list) \
const char *fnname(int val) \
......@@ -515,9 +420,6 @@ int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb,
if (ret)
goto out;
/* Grab the idr reference. */
drm_framebuffer_reference(fb);
dev->mode_config.num_fb++;
list_add(&fb->head, &dev->mode_config.fb_list);
out:
......@@ -527,10 +429,34 @@ int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb,
}
EXPORT_SYMBOL(drm_framebuffer_init);
/* dev->mode_config.fb_lock must be held! */
static void __drm_framebuffer_unregister(struct drm_device *dev,
struct drm_framebuffer *fb)
{
mutex_lock(&dev->mode_config.idr_mutex);
idr_remove(&dev->mode_config.crtc_idr, fb->base.id);
mutex_unlock(&dev->mode_config.idr_mutex);
fb->base.id = 0;
}
static void drm_framebuffer_free(struct kref *kref)
{
struct drm_framebuffer *fb =
container_of(kref, struct drm_framebuffer, refcount);
struct drm_device *dev = fb->dev;
/*
* The lookup idr holds a weak reference, which has not necessarily been
* removed at this point. Check for that.
*/
mutex_lock(&dev->mode_config.fb_lock);
if (fb->base.id) {
/* Mark fb as reaped and drop idr ref. */
__drm_framebuffer_unregister(dev, fb);
}
mutex_unlock(&dev->mode_config.fb_lock);
fb->funcs->destroy(fb);
}
......@@ -567,8 +493,10 @@ struct drm_framebuffer *drm_framebuffer_lookup(struct drm_device *dev,
mutex_lock(&dev->mode_config.fb_lock);
fb = __drm_framebuffer_lookup(dev, id);
if (fb)
drm_framebuffer_reference(fb);
if (fb) {
if (!kref_get_unless_zero(&fb->refcount))
fb = NULL;
}
mutex_unlock(&dev->mode_config.fb_lock);
return fb;
......@@ -612,19 +540,6 @@ static void __drm_framebuffer_unreference(struct drm_framebuffer *fb)
kref_put(&fb->refcount, drm_framebuffer_free_bug);
}
/* dev->mode_config.fb_lock must be held! */
static void __drm_framebuffer_unregister(struct drm_device *dev,
struct drm_framebuffer *fb)
{
mutex_lock(&dev->mode_config.idr_mutex);
idr_remove(&dev->mode_config.crtc_idr, fb->base.id);
mutex_unlock(&dev->mode_config.idr_mutex);
fb->base.id = 0;
__drm_framebuffer_unreference(fb);
}
/**
* drm_framebuffer_unregister_private - unregister a private fb from the lookup idr
* @fb: fb to unregister
......@@ -852,6 +767,59 @@ static void drm_mode_remove(struct drm_connector *connector,
drm_mode_destroy(connector->dev, mode);
}
/**
* drm_connector_get_cmdline_mode - reads the user's cmdline mode
* @connector: connector to quwery
* @mode: returned mode
*
* The kernel supports per-connector configration of its consoles through
* use of the video= parameter. This function parses that option and
* extracts the user's specified mode (or enable/disable status) for a
* particular connector. This is typically only used during the early fbdev
* setup.
*/
static void drm_connector_get_cmdline_mode(struct drm_connector *connector)
{
struct drm_cmdline_mode *mode = &connector->cmdline_mode;
char *option = NULL;
if (fb_get_options(connector->name, &option))
return;
if (!drm_mode_parse_command_line_for_connector(option,
connector,
mode))
return;
if (mode->force) {
const char *s;
switch (mode->force) {
case DRM_FORCE_OFF:
s = "OFF";
break;
case DRM_FORCE_ON_DIGITAL:
s = "ON - dig";
break;
default:
case DRM_FORCE_ON:
s = "ON";
break;
}
DRM_INFO("forcing %s connector %s\n", connector->name, s);
connector->force = mode->force;
}
DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n",
connector->name,
mode->xres, mode->yres,
mode->refresh_specified ? mode->refresh : 60,
mode->rb ? " reduced blanking" : "",
mode->margins ? " with margins" : "",
mode->interlace ? " interlaced" : "");
}
/**
* drm_connector_init - Init a preallocated connector
* @dev: DRM device
......@@ -904,6 +872,8 @@ int drm_connector_init(struct drm_device *dev,
connector->edid_blob_ptr = NULL;
connector->status = connector_status_unknown;
drm_connector_get_cmdline_mode(connector);
list_add_tail(&connector->head, &dev->mode_config.connector_list);
dev->mode_config.num_connector++;
......@@ -956,6 +926,29 @@ void drm_connector_cleanup(struct drm_connector *connector)
}
EXPORT_SYMBOL(drm_connector_cleanup);
/**
* drm_connector_index - find the index of a registered connector
* @connector: connector to find index for
*
* Given a registered connector, return the index of that connector within a DRM
* device's list of connectors.
*/
unsigned int drm_connector_index(struct drm_connector *connector)
{
unsigned int index = 0;
struct drm_connector *tmp;
list_for_each_entry(tmp, &connector->dev->mode_config.connector_list, head) {
if (tmp == connector)
return index;
index++;
}
BUG();
}
EXPORT_SYMBOL(drm_connector_index);
/**
* drm_connector_register - register a connector
* @connector: the connector to register
......@@ -1260,6 +1253,29 @@ void drm_plane_cleanup(struct drm_plane *plane)
}
EXPORT_SYMBOL(drm_plane_cleanup);
/**
* drm_plane_index - find the index of a registered plane
* @plane: plane to find index for
*
* Given a registered plane, return the index of that CRTC within a DRM
* device's list of planes.
*/
unsigned int drm_plane_index(struct drm_plane *plane)
{
unsigned int index = 0;
struct drm_plane *tmp;
list_for_each_entry(tmp, &plane->dev->mode_config.plane_list, head) {
if (tmp == plane)
return index;
index++;
}
BUG();
}
EXPORT_SYMBOL(drm_plane_index);
/**
* drm_plane_force_disable - Forcibly disable a plane
* @plane: plane to disable
......@@ -1271,19 +1287,21 @@ EXPORT_SYMBOL(drm_plane_cleanup);
*/
void drm_plane_force_disable(struct drm_plane *plane)
{
struct drm_framebuffer *old_fb = plane->fb;
int ret;
if (!old_fb)
if (!plane->fb)
return;
plane->old_fb = plane->fb;
ret = plane->funcs->disable_plane(plane);
if (ret) {
DRM_ERROR("failed to disable plane with busy fb\n");
plane->old_fb = NULL;
return;
}
/* disconnect the plane from the fb and crtc: */
__drm_framebuffer_unreference(old_fb);
__drm_framebuffer_unreference(plane->old_fb);
plane->old_fb = NULL;
plane->fb = NULL;
plane->crtc = NULL;
}
......@@ -2259,23 +2277,21 @@ static int setplane_internal(struct drm_plane *plane,
uint32_t src_w, uint32_t src_h)
{
struct drm_device *dev = plane->dev;
struct drm_framebuffer *old_fb = NULL;
int ret = 0;
unsigned int fb_width, fb_height;
int i;
drm_modeset_lock_all(dev);
/* No fb means shut it down */
if (!fb) {
drm_modeset_lock_all(dev);
old_fb = plane->fb;
plane->old_fb = plane->fb;
ret = plane->funcs->disable_plane(plane);
if (!ret) {
plane->crtc = NULL;
plane->fb = NULL;
} else {
old_fb = NULL;
plane->old_fb = NULL;
}
drm_modeset_unlock_all(dev);
goto out;
}
......@@ -2315,8 +2331,7 @@ static int setplane_internal(struct drm_plane *plane,
goto out;
}
drm_modeset_lock_all(dev);
old_fb = plane->fb;
plane->old_fb = plane->fb;
ret = plane->funcs->update_plane(plane, crtc, fb,
crtc_x, crtc_y, crtc_w, crtc_h,
src_x, src_y, src_w, src_h);
......@@ -2325,15 +2340,16 @@ static int setplane_internal(struct drm_plane *plane,
plane->fb = fb;
fb = NULL;
} else {
old_fb = NULL;
plane->old_fb = NULL;
}
drm_modeset_unlock_all(dev);
out:
if (fb)
drm_framebuffer_unreference(fb);
if (old_fb)
drm_framebuffer_unreference(old_fb);
if (plane->old_fb)
drm_framebuffer_unreference(plane->old_fb);
plane->old_fb = NULL;
drm_modeset_unlock_all(dev);
return ret;
......@@ -2440,7 +2456,7 @@ int drm_mode_set_config_internal(struct drm_mode_set *set)
* crtcs. Atomic modeset will have saner semantics ...
*/
list_for_each_entry(tmp, &crtc->dev->mode_config.crtc_list, head)
tmp->old_fb = tmp->primary->fb;
tmp->primary->old_fb = tmp->primary->fb;
fb = set->fb;
......@@ -2453,8 +2469,9 @@ int drm_mode_set_config_internal(struct drm_mode_set *set)
list_for_each_entry(tmp, &crtc->dev->mode_config.crtc_list, head) {
if (tmp->primary->fb)
drm_framebuffer_reference(tmp->primary->fb);
if (tmp->old_fb)
drm_framebuffer_unreference(tmp->old_fb);
if (tmp->primary->old_fb)
drm_framebuffer_unreference(tmp->primary->old_fb);
tmp->primary->old_fb = NULL;
}
return ret;
......@@ -2785,7 +2802,7 @@ static int drm_mode_cursor_common(struct drm_device *dev,
if (crtc->cursor)
return drm_mode_cursor_universal(crtc, req, file_priv);
drm_modeset_lock(&crtc->mutex, NULL);
drm_modeset_lock_crtc(crtc);
if (req->flags & DRM_MODE_CURSOR_BO) {
if (!crtc->funcs->cursor_set && !crtc->funcs->cursor_set2) {
ret = -ENXIO;
......@@ -2809,7 +2826,7 @@ static int drm_mode_cursor_common(struct drm_device *dev,
}
}
out:
drm_modeset_unlock(&crtc->mutex);
drm_modeset_unlock_crtc(crtc);
return ret;
......@@ -3495,9 +3512,10 @@ EXPORT_SYMBOL(drm_property_create_enum);
* @flags: flags specifying the property type
* @name: name of the property
* @props: enumeration lists with property bitflags
* @num_values: number of pre-defined values
* @num_props: size of the @props array
* @supported_bits: bitmask of all supported enumeration values
*
* This creates a new generic drm property which can then be attached to a drm
* This creates a new bitmask drm property which can then be attached to a drm
* object with drm_object_attach_property. The returned property object must be
* freed with drm_property_destroy.
*
......@@ -4529,7 +4547,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
{
struct drm_mode_crtc_page_flip *page_flip = data;
struct drm_crtc *crtc;
struct drm_framebuffer *fb = NULL, *old_fb = NULL;
struct drm_framebuffer *fb = NULL;
struct drm_pending_vblank_event *e = NULL;
unsigned long flags;
int ret = -EINVAL;
......@@ -4545,7 +4563,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
if (!crtc)
return -ENOENT;
drm_modeset_lock(&crtc->mutex, NULL);
drm_modeset_lock_crtc(crtc);
if (crtc->primary->fb == NULL) {
/* The framebuffer is currently unbound, presumably
* due to a hotplug event, that userspace has not
......@@ -4601,7 +4619,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
(void (*) (struct drm_pending_event *)) kfree;
}
old_fb = crtc->primary->fb;
crtc->primary->old_fb = crtc->primary->fb;
ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags);
if (ret) {
if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
......@@ -4611,7 +4629,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
kfree(e);
}
/* Keep the old fb, don't unref it. */
old_fb = NULL;
crtc->primary->old_fb = NULL;
} else {
/*
* Warn if the driver hasn't properly updated the crtc->fb
......@@ -4627,9 +4645,10 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
out:
if (fb)
drm_framebuffer_unreference(fb);
if (old_fb)
drm_framebuffer_unreference(old_fb);
drm_modeset_unlock(&crtc->mutex);
if (crtc->primary->old_fb)
drm_framebuffer_unreference(crtc->primary->old_fb);
crtc->primary->old_fb = NULL;
drm_modeset_unlock_crtc(crtc);
return ret;
}
......@@ -4645,9 +4664,14 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
void drm_mode_config_reset(struct drm_device *dev)
{
struct drm_crtc *crtc;
struct drm_plane *plane;
struct drm_encoder *encoder;
struct drm_connector *connector;
list_for_each_entry(plane, &dev->mode_config.plane_list, head)
if (plane->funcs->reset)
plane->funcs->reset(plane);
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
if (crtc->funcs->reset)
crtc->funcs->reset(crtc);
......
......@@ -1772,7 +1772,7 @@ static int drm_dp_get_vc_payload_bw(int dp_link_bw, int dp_link_count)
case DP_LINK_BW_5_4:
return 10 * dp_link_count;
}
return 0;
BUG();
}
/**
......@@ -2071,6 +2071,7 @@ static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
* drm_dp_mst_hpd_irq() - MST hotplug IRQ notify
* @mgr: manager to notify irq for.
* @esi: 4 bytes from SINK_COUNT_ESI
* @handled: whether the hpd interrupt was consumed or not
*
* This should be called from the driver when it detects a short IRQ,
* along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The
......
......@@ -3433,10 +3433,10 @@ EXPORT_SYMBOL(drm_rgb_quant_range_selectable);
/**
* drm_assign_hdmi_deep_color_info - detect whether monitor supports
* hdmi deep color modes and update drm_display_info if so.
*
* @edid: monitor EDID information
* @info: Updated with maximum supported deep color bpc and color format
* if deep color supported.
* @connector: DRM connector, used only for debug output
*
* Parse the CEA extension according to CEA-861-B.
* Return true if HDMI deep color supported, false if not or unknown.
......
......@@ -126,7 +126,7 @@ int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_
WARN_ON(!mutex_is_locked(&fb_helper->dev->mode_config.mutex));
if (fb_helper->connector_count + 1 > fb_helper->connector_info_alloc_count) {
temp = krealloc(fb_helper->connector_info, sizeof(struct drm_fb_helper_connector) * (fb_helper->connector_count + 1), GFP_KERNEL);
temp = krealloc(fb_helper->connector_info, sizeof(struct drm_fb_helper_connector *) * (fb_helper->connector_count + 1), GFP_KERNEL);
if (!temp)
return -ENOMEM;
......@@ -171,60 +171,6 @@ int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper,
}
EXPORT_SYMBOL(drm_fb_helper_remove_one_connector);
static int drm_fb_helper_parse_command_line(struct drm_fb_helper *fb_helper)
{
struct drm_fb_helper_connector *fb_helper_conn;
int i;
for (i = 0; i < fb_helper->connector_count; i++) {
struct drm_cmdline_mode *mode;
struct drm_connector *connector;
char *option = NULL;
fb_helper_conn = fb_helper->connector_info[i];
connector = fb_helper_conn->connector;
mode = &fb_helper_conn->cmdline_mode;
/* do something on return - turn off connector maybe */
if (fb_get_options(connector->name, &option))
continue;
if (drm_mode_parse_command_line_for_connector(option,
connector,
mode)) {
if (mode->force) {
const char *s;
switch (mode->force) {
case DRM_FORCE_OFF:
s = "OFF";
break;
case DRM_FORCE_ON_DIGITAL:
s = "ON - dig";
break;
default:
case DRM_FORCE_ON:
s = "ON";
break;
}
DRM_INFO("forcing %s connector %s\n",
connector->name, s);
connector->force = mode->force;
}
DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n",
connector->name,
mode->xres, mode->yres,
mode->refresh_specified ? mode->refresh : 60,
mode->rb ? " reduced blanking" : "",
mode->margins ? " with margins" : "",
mode->interlace ? " interlaced" : "");
}
}
return 0;
}
static void drm_fb_helper_save_lut_atomic(struct drm_crtc *crtc, struct drm_fb_helper *helper)
{
uint16_t *r_base, *g_base, *b_base;
......@@ -419,11 +365,11 @@ static bool drm_fb_helper_force_kernel_mode(void)
if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
continue;
/* NOTE: we use lockless flag below to avoid grabbing other
* modeset locks. So just trylock the underlying mutex
* directly:
/*
* NOTE: Use trylock mode to avoid deadlocks and sleeping in
* panic context.
*/
if (!mutex_trylock(&dev->mode_config.mutex)) {
if (__drm_modeset_lock_all(dev, true) != 0) {
error = true;
continue;
}
......@@ -432,7 +378,7 @@ static bool drm_fb_helper_force_kernel_mode(void)
if (ret)
error = true;
mutex_unlock(&dev->mode_config.mutex);
drm_modeset_unlock_all(dev);
}
return error;
}
......@@ -1013,7 +959,7 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
struct drm_fb_helper_connector *fb_helper_conn = fb_helper->connector_info[i];
struct drm_cmdline_mode *cmdline_mode;
cmdline_mode = &fb_helper_conn->cmdline_mode;
cmdline_mode = &fb_helper_conn->connector->cmdline_mode;
if (cmdline_mode->bpp_specified) {
switch (cmdline_mode->bpp) {
......@@ -1260,9 +1206,7 @@ EXPORT_SYMBOL(drm_has_preferred_mode);
static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector)
{
struct drm_cmdline_mode *cmdline_mode;
cmdline_mode = &fb_connector->cmdline_mode;
return cmdline_mode->specified;
return fb_connector->connector->cmdline_mode.specified;
}
struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn,
......@@ -1272,7 +1216,7 @@ struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *f
struct drm_display_mode *mode = NULL;
bool prefer_non_interlace;
cmdline_mode = &fb_helper_conn->cmdline_mode;
cmdline_mode = &fb_helper_conn->connector->cmdline_mode;
if (cmdline_mode->specified == false)
return mode;
......@@ -1657,8 +1601,6 @@ bool drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel)
struct drm_device *dev = fb_helper->dev;
int count = 0;
drm_fb_helper_parse_command_line(fb_helper);
mutex_lock(&dev->mode_config.mutex);
count = drm_fb_helper_probe_connector_modes(fb_helper,
dev->mode_config.max_width,
......
......@@ -464,6 +464,8 @@ int drm_release(struct inode *inode, struct file *filp)
if (drm_core_check_feature(dev, DRIVER_PRIME))
drm_prime_destroy_file_private(&file_priv->prime);
WARN_ON(!list_empty(&file_priv->event_list));
put_pid(file_priv->pid);
kfree(file_priv);
......
......@@ -720,6 +720,8 @@ EXPORT_SYMBOL(drm_get_last_vbltimestamp);
*/
u32 drm_vblank_count(struct drm_device *dev, int crtc)
{
if (WARN_ON(crtc >= dev->num_crtcs))
return 0;
return atomic_read(&dev->vblank[crtc].count);
}
EXPORT_SYMBOL(drm_vblank_count);
......@@ -742,6 +744,9 @@ u32 drm_vblank_count_and_time(struct drm_device *dev, int crtc,
{
u32 cur_vblank;
if (WARN_ON(crtc >= dev->num_crtcs))
return 0;
/* Read timestamp from slot of _vblank_time ringbuffer
* that corresponds to current vblank count. Retry if
* count has incremented during readout. This works like
......@@ -917,6 +922,9 @@ int drm_vblank_get(struct drm_device *dev, int crtc)
unsigned long irqflags;
int ret = 0;
if (WARN_ON(crtc >= dev->num_crtcs))
return -EINVAL;
spin_lock_irqsave(&dev->vbl_lock, irqflags);
/* Going from 0->1 means we have to enable interrupts again */
if (atomic_add_return(1, &dev->vblank[crtc].refcount) == 1) {
......@@ -965,6 +973,9 @@ void drm_vblank_put(struct drm_device *dev, int crtc)
{
BUG_ON(atomic_read(&dev->vblank[crtc].refcount) == 0);
if (WARN_ON(crtc >= dev->num_crtcs))
return;
/* Last user schedules interrupt disable */
if (atomic_dec_and_test(&dev->vblank[crtc].refcount) &&
(drm_vblank_offdelay > 0))
......@@ -988,6 +999,50 @@ void drm_crtc_vblank_put(struct drm_crtc *crtc)
}
EXPORT_SYMBOL(drm_crtc_vblank_put);
/**
* drm_wait_one_vblank - wait for one vblank
* @dev: DRM device
* @crtc: crtc index
*
* This waits for one vblank to pass on @crtc, using the irq driver interfaces.
* It is a failure to call this when the vblank irq for @crtc is disabled, e.g.
* due to lack of driver support or because the crtc is off.
*/
void drm_wait_one_vblank(struct drm_device *dev, int crtc)
{
int ret;
u32 last;
ret = drm_vblank_get(dev, crtc);
if (WARN_ON(ret))
return;
last = drm_vblank_count(dev, crtc);
ret = wait_event_timeout(dev->vblank[crtc].queue,
last != drm_vblank_count(dev, crtc),
msecs_to_jiffies(100));
WARN_ON(ret == 0);
drm_vblank_put(dev, crtc);
}
EXPORT_SYMBOL(drm_wait_one_vblank);
/**
* drm_crtc_wait_one_vblank - wait for one vblank
* @crtc: DRM crtc
*
* This waits for one vblank to pass on @crtc, using the irq driver interfaces.
* It is a failure to call this when the vblank irq for @crtc is disabled, e.g.
* due to lack of driver support or because the crtc is off.
*/
void drm_crtc_wait_one_vblank(struct drm_crtc *crtc)
{
drm_wait_one_vblank(crtc->dev, drm_crtc_index(crtc));
}
EXPORT_SYMBOL(drm_crtc_wait_one_vblank);
/**
* drm_vblank_off - disable vblank events on a CRTC
* @dev: DRM device
......@@ -1009,6 +1064,9 @@ void drm_vblank_off(struct drm_device *dev, int crtc)
unsigned long irqflags;
unsigned int seq;
if (WARN_ON(crtc >= dev->num_crtcs))
return;
spin_lock_irqsave(&dev->vbl_lock, irqflags);
vblank_disable_and_save(dev, crtc);
wake_up(&dev->vblank[crtc].queue);
......@@ -1068,6 +1126,9 @@ void drm_vblank_on(struct drm_device *dev, int crtc)
{
unsigned long irqflags;
if (WARN_ON(crtc >= dev->num_crtcs))
return;
spin_lock_irqsave(&dev->vbl_lock, irqflags);
/* re-enable interrupts if there's are users left */
if (atomic_read(&dev->vblank[crtc].refcount) != 0)
......@@ -1121,6 +1182,10 @@ void drm_vblank_pre_modeset(struct drm_device *dev, int crtc)
/* vblank is not initialized (IRQ not installed ?), or has been freed */
if (!dev->num_crtcs)
return;
if (WARN_ON(crtc >= dev->num_crtcs))
return;
/*
* To avoid all the problems that might happen if interrupts
* were enabled/disabled around or between these calls, we just
......@@ -1429,6 +1494,9 @@ bool drm_handle_vblank(struct drm_device *dev, int crtc)
if (!dev->num_crtcs)
return false;
if (WARN_ON(crtc >= dev->num_crtcs))
return false;
/* Need timestamp lock to prevent concurrent execution with
* vblank enable/disable, as this would cause inconsistent
* or corrupted timestamps and vblank counts.
......
......@@ -1259,6 +1259,7 @@ drm_mode_create_from_cmdline_mode(struct drm_device *dev,
if (!mode)
return NULL;
mode->type |= DRM_MODE_TYPE_USERDEF;
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
return mode;
}
......
......@@ -56,6 +56,212 @@
*/
/**
* __drm_modeset_lock_all - internal helper to grab all modeset locks
* @dev: DRM device
* @trylock: trylock mode for atomic contexts
*
* This is a special version of drm_modeset_lock_all() which can also be used in
* atomic contexts. Then @trylock must be set to true.
*
* Returns:
* 0 on success or negative error code on failure.
*/
int __drm_modeset_lock_all(struct drm_device *dev,
bool trylock)
{
struct drm_mode_config *config = &dev->mode_config;
struct drm_modeset_acquire_ctx *ctx;
int ret;
ctx = kzalloc(sizeof(*ctx),
trylock ? GFP_ATOMIC : GFP_KERNEL);
if (!ctx)
return -ENOMEM;
if (trylock) {
if (!mutex_trylock(&config->mutex))
return -EBUSY;
} else {
mutex_lock(&config->mutex);
}
drm_modeset_acquire_init(ctx, 0);
ctx->trylock_only = trylock;
retry:
ret = drm_modeset_lock(&config->connection_mutex, ctx);
if (ret)
goto fail;
ret = drm_modeset_lock_all_crtcs(dev, ctx);
if (ret)
goto fail;
WARN_ON(config->acquire_ctx);
/* now we hold the locks, so now that it is safe, stash the
* ctx for drm_modeset_unlock_all():
*/
config->acquire_ctx = ctx;
drm_warn_on_modeset_not_all_locked(dev);
return 0;
fail:
if (ret == -EDEADLK) {
drm_modeset_backoff(ctx);
goto retry;
}
return ret;
}
EXPORT_SYMBOL(__drm_modeset_lock_all);
/**
* drm_modeset_lock_all - take all modeset locks
* @dev: drm device
*
* This function takes all modeset locks, suitable where a more fine-grained
* scheme isn't (yet) implemented. Locks must be dropped with
* drm_modeset_unlock_all.
*/
void drm_modeset_lock_all(struct drm_device *dev)
{
WARN_ON(__drm_modeset_lock_all(dev, false) != 0);
}
EXPORT_SYMBOL(drm_modeset_lock_all);
/**
* drm_modeset_unlock_all - drop all modeset locks
* @dev: device
*
* This function drop all modeset locks taken by drm_modeset_lock_all.
*/
void drm_modeset_unlock_all(struct drm_device *dev)
{
struct drm_mode_config *config = &dev->mode_config;
struct drm_modeset_acquire_ctx *ctx = config->acquire_ctx;
if (WARN_ON(!ctx))
return;
config->acquire_ctx = NULL;
drm_modeset_drop_locks(ctx);
drm_modeset_acquire_fini(ctx);
kfree(ctx);
mutex_unlock(&dev->mode_config.mutex);
}
EXPORT_SYMBOL(drm_modeset_unlock_all);
/**
* drm_modeset_lock_crtc - lock crtc with hidden acquire ctx
* @crtc: drm crtc
*
* This function locks the given crtc using a hidden acquire context. This is
* necessary so that drivers internally using the atomic interfaces can grab
* further locks with the lock acquire context.
*/
void drm_modeset_lock_crtc(struct drm_crtc *crtc)
{
struct drm_modeset_acquire_ctx *ctx;
int ret;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (WARN_ON(!ctx))
return;
drm_modeset_acquire_init(ctx, 0);
retry:
ret = drm_modeset_lock(&crtc->mutex, ctx);
if (ret)
goto fail;
WARN_ON(crtc->acquire_ctx);
/* now we hold the locks, so now that it is safe, stash the
* ctx for drm_modeset_unlock_crtc():
*/
crtc->acquire_ctx = ctx;
return;
fail:
if (ret == -EDEADLK) {
drm_modeset_backoff(ctx);
goto retry;
}
}
EXPORT_SYMBOL(drm_modeset_lock_crtc);
/**
* drm_modeset_legacy_acquire_ctx - find acquire ctx for legacy ioctls
* @crtc: drm crtc
*
* Legacy ioctl operations like cursor updates or page flips only have per-crtc
* locking, and store the acquire ctx in the corresponding crtc. All other
* legacy operations take all locks and use a global acquire context. This
* function grabs the right one.
*/
struct drm_modeset_acquire_ctx *
drm_modeset_legacy_acquire_ctx(struct drm_crtc *crtc)
{
if (crtc->acquire_ctx)
return crtc->acquire_ctx;
WARN_ON(!crtc->dev->mode_config.acquire_ctx);
return crtc->dev->mode_config.acquire_ctx;
}
EXPORT_SYMBOL(drm_modeset_legacy_acquire_ctx);
/**
* drm_modeset_unlock_crtc - drop crtc lock
* @crtc: drm crtc
*
* This drops the crtc lock acquire with drm_modeset_lock_crtc() and all other
* locks acquired through the hidden context.
*/
void drm_modeset_unlock_crtc(struct drm_crtc *crtc)
{
struct drm_modeset_acquire_ctx *ctx = crtc->acquire_ctx;
if (WARN_ON(!ctx))
return;
crtc->acquire_ctx = NULL;
drm_modeset_drop_locks(ctx);
drm_modeset_acquire_fini(ctx);
kfree(ctx);
}
EXPORT_SYMBOL(drm_modeset_unlock_crtc);
/**
* drm_warn_on_modeset_not_all_locked - check that all modeset locks are locked
* @dev: device
*
* Useful as a debug assert.
*/
void drm_warn_on_modeset_not_all_locked(struct drm_device *dev)
{
struct drm_crtc *crtc;
/* Locking is currently fubar in the panic handler. */
if (oops_in_progress)
return;
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
WARN_ON(!drm_modeset_is_locked(&crtc->mutex));
WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex));
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
}
EXPORT_SYMBOL(drm_warn_on_modeset_not_all_locked);
/**
* drm_modeset_acquire_init - initialize acquire context
* @ctx: the acquire context
......@@ -108,7 +314,12 @@ static inline int modeset_lock(struct drm_modeset_lock *lock,
WARN_ON(ctx->contended);
if (interruptible && slow) {
if (ctx->trylock_only) {
if (!ww_mutex_trylock(&lock->mutex))
return -EBUSY;
else
return 0;
} else if (interruptible && slow) {
ret = ww_mutex_lock_slow_interruptible(&lock->mutex, &ctx->ww_ctx);
} else if (interruptible) {
ret = ww_mutex_lock_interruptible(&lock->mutex, &ctx->ww_ctx);
......
......@@ -82,6 +82,22 @@ static void drm_mode_validate_flag(struct drm_connector *connector,
return;
}
static int drm_helper_probe_add_cmdline_mode(struct drm_connector *connector)
{
struct drm_display_mode *mode;
if (!connector->cmdline_mode.specified)
return 0;
mode = drm_mode_create_from_cmdline_mode(connector->dev,
&connector->cmdline_mode);
if (mode == NULL)
return 0;
drm_mode_probed_add(connector, mode);
return 1;
}
static int drm_helper_probe_single_connector_modes_merge_bits(struct drm_connector *connector,
uint32_t maxX, uint32_t maxY, bool merge_type_bits)
{
......@@ -141,6 +157,7 @@ static int drm_helper_probe_single_connector_modes_merge_bits(struct drm_connect
if (count == 0 && connector->status == connector_status_connected)
count = drm_add_modes_noedid(connector, 1024, 768);
count += drm_helper_probe_add_cmdline_mode(connector);
if (count == 0)
goto prune;
......
......@@ -1483,11 +1483,7 @@ static int mga_vga_mode_valid(struct drm_connector *connector,
{
struct drm_device *dev = connector->dev;
struct mga_device *mdev = (struct mga_device*)dev->dev_private;
struct mga_fbdev *mfbdev = mdev->mfbdev;
struct drm_fb_helper *fb_helper = &mfbdev->helper;
struct drm_fb_helper_connector *fb_helper_conn = NULL;
int bpp = 32;
int i = 0;
if (IS_G200_SE(mdev)) {
if (mdev->unique_rev_id == 0x01) {
......@@ -1537,21 +1533,14 @@ static int mga_vga_mode_valid(struct drm_connector *connector,
}
/* Validate the mode input by the user */
for (i = 0; i < fb_helper->connector_count; i++) {
if (fb_helper->connector_info[i]->connector == connector) {
/* Found the helper for this connector */
fb_helper_conn = fb_helper->connector_info[i];
if (fb_helper_conn->cmdline_mode.specified) {
if (fb_helper_conn->cmdline_mode.bpp_specified) {
bpp = fb_helper_conn->cmdline_mode.bpp;
}
}
}
if (connector->cmdline_mode.specified) {
if (connector->cmdline_mode.bpp_specified)
bpp = connector->cmdline_mode.bpp;
}
if ((mode->hdisplay * mode->vdisplay * (bpp/8)) > mdev->mc.vram_size) {
if (fb_helper_conn)
fb_helper_conn->cmdline_mode.specified = false;
if (connector->cmdline_mode.specified)
connector->cmdline_mode.specified = false;
return MODE_BAD;
}
......
......@@ -4,6 +4,7 @@
menuconfig FB
tristate "Support for frame buffer devices"
select FB_CMDLINE
---help---
The frame buffer device provides an abstraction for the graphics
hardware. It represents the frame buffer of some video hardware and
......@@ -52,6 +53,9 @@ config FIRMWARE_EDID
combination with certain motherboards and monitors are known to
suffer from this problem.
config FB_CMDLINE
bool
config FB_DDC
tristate
depends on FB
......
obj-y += fb_notify.o
obj-$(CONFIG_FB_CMDLINE) += fb_cmdline.o
obj-$(CONFIG_FB) += fb.o
fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
modedb.o fbcvt.o
......
/*
* linux/drivers/video/fb_cmdline.c
*
* Copyright (C) 2014 Intel Corp
* Copyright (C) 1994 Martin Schaller
*
* 2001 - Documented with DocBook
* - Brad Douglas <brad@neruo.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
* Authors:
* Vetter <danie.vetter@ffwll.ch>
*/
#include <linux/init.h>
#include <linux/fb.h>
static char *video_options[FB_MAX] __read_mostly;
static int ofonly __read_mostly;
const char *fb_mode_option;
EXPORT_SYMBOL_GPL(fb_mode_option);
/**
* fb_get_options - get kernel boot parameters
* @name: framebuffer name as it would appear in
* the boot parameter line
* (video=<name>:<options>)
* @option: the option will be stored here
*
* NOTE: Needed to maintain backwards compatibility
*/
int fb_get_options(const char *name, char **option)
{
char *opt, *options = NULL;
int retval = 0;
int name_len = strlen(name), i;
if (name_len && ofonly && strncmp(name, "offb", 4))
retval = 1;
if (name_len && !retval) {
for (i = 0; i < FB_MAX; i++) {
if (video_options[i] == NULL)
continue;
if (!video_options[i][0])
continue;
opt = video_options[i];
if (!strncmp(name, opt, name_len) &&
opt[name_len] == ':')
options = opt + name_len + 1;
}
}
/* No match, pass global option */
if (!options && option && fb_mode_option)
options = kstrdup(fb_mode_option, GFP_KERNEL);
if (options && !strncmp(options, "off", 3))
retval = 1;
if (option)
*option = options;
return retval;
}
EXPORT_SYMBOL(fb_get_options);
/**
* video_setup - process command line options
* @options: string of options
*
* Process command line options for frame buffer subsystem.
*
* NOTE: This function is a __setup and __init function.
* It only stores the options. Drivers have to call
* fb_get_options() as necessary.
*
* Returns zero.
*
*/
static int __init video_setup(char *options)
{
int i, global = 0;
if (!options || !*options)
global = 1;
if (!global && !strncmp(options, "ofonly", 6)) {
ofonly = 1;
global = 1;
}
if (!global && !strchr(options, ':')) {
fb_mode_option = options;
global = 1;
}
if (!global) {
for (i = 0; i < FB_MAX; i++) {
if (video_options[i] == NULL) {
video_options[i] = options;
break;
}
}
}
return 1;
}
__setup("video=", video_setup);
......@@ -1908,96 +1908,4 @@ int fb_new_modelist(struct fb_info *info)
return err;
}
static char *video_options[FB_MAX] __read_mostly;
static int ofonly __read_mostly;
/**
* fb_get_options - get kernel boot parameters
* @name: framebuffer name as it would appear in
* the boot parameter line
* (video=<name>:<options>)
* @option: the option will be stored here
*
* NOTE: Needed to maintain backwards compatibility
*/
int fb_get_options(const char *name, char **option)
{
char *opt, *options = NULL;
int retval = 0;
int name_len = strlen(name), i;
if (name_len && ofonly && strncmp(name, "offb", 4))
retval = 1;
if (name_len && !retval) {
for (i = 0; i < FB_MAX; i++) {
if (video_options[i] == NULL)
continue;
if (!video_options[i][0])
continue;
opt = video_options[i];
if (!strncmp(name, opt, name_len) &&
opt[name_len] == ':')
options = opt + name_len + 1;
}
}
/* No match, pass global option */
if (!options && option && fb_mode_option)
options = kstrdup(fb_mode_option, GFP_KERNEL);
if (options && !strncmp(options, "off", 3))
retval = 1;
if (option)
*option = options;
return retval;
}
EXPORT_SYMBOL(fb_get_options);
#ifndef MODULE
/**
* video_setup - process command line options
* @options: string of options
*
* Process command line options for frame buffer subsystem.
*
* NOTE: This function is a __setup and __init function.
* It only stores the options. Drivers have to call
* fb_get_options() as necessary.
*
* Returns zero.
*
*/
static int __init video_setup(char *options)
{
int i, global = 0;
if (!options || !*options)
global = 1;
if (!global && !strncmp(options, "ofonly", 6)) {
ofonly = 1;
global = 1;
}
if (!global && !strchr(options, ':')) {
fb_mode_option = options;
global = 1;
}
if (!global) {
for (i = 0; i < FB_MAX; i++) {
if (video_options[i] == NULL) {
video_options[i] = options;
break;
}
}
}
return 1;
}
__setup("video=", video_setup);
#endif
MODULE_LICENSE("GPL");
......@@ -29,9 +29,6 @@
#define DPRINTK(fmt, args...)
#endif
const char *fb_mode_option;
EXPORT_SYMBOL_GPL(fb_mode_option);
/*
* Standard video mode definitions (taken from XFree86)
*/
......
......@@ -1294,6 +1294,8 @@ extern int drm_vblank_get(struct drm_device *dev, int crtc);
extern void drm_vblank_put(struct drm_device *dev, int crtc);
extern int drm_crtc_vblank_get(struct drm_crtc *crtc);
extern void drm_crtc_vblank_put(struct drm_crtc *crtc);
extern void drm_wait_one_vblank(struct drm_device *dev, int crtc);
extern void drm_crtc_wait_one_vblank(struct drm_crtc *crtc);
extern void drm_vblank_off(struct drm_device *dev, int crtc);
extern void drm_vblank_on(struct drm_device *dev, int crtc);
extern void drm_crtc_vblank_off(struct drm_crtc *crtc);
......
......@@ -218,10 +218,6 @@ struct drm_property {
struct list_head enum_blob_list;
};
void drm_modeset_lock_all(struct drm_device *dev);
void drm_modeset_unlock_all(struct drm_device *dev);
void drm_warn_on_modeset_not_all_locked(struct drm_device *dev);
struct drm_crtc;
struct drm_connector;
struct drm_encoder;
......@@ -345,10 +341,6 @@ struct drm_crtc {
int cursor_x;
int cursor_y;
/* Temporary tracking of the old fb while a modeset is ongoing. Used
* by drm_mode_set_config_internal to implement correct refcounting. */
struct drm_framebuffer *old_fb;
bool enabled;
/* Requested mode from modesetting. */
......@@ -375,6 +367,12 @@ struct drm_crtc {
void *helper_private;
struct drm_object_properties properties;
/*
* For legacy crtc ioctls so that atomic drivers can get at the locking
* acquire context.
*/
struct drm_modeset_acquire_ctx *acquire_ctx;
};
......@@ -548,6 +546,7 @@ struct drm_connector {
void *helper_private;
/* forced on connector */
struct drm_cmdline_mode cmdline_mode;
enum drm_connector_force force;
bool override_edid;
uint32_t encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
......@@ -582,6 +581,7 @@ struct drm_plane_funcs {
uint32_t src_w, uint32_t src_h);
int (*disable_plane)(struct drm_plane *plane);
void (*destroy)(struct drm_plane *plane);
void (*reset)(struct drm_plane *plane);
int (*set_property)(struct drm_plane *plane,
struct drm_property *property, uint64_t val);
......@@ -620,6 +620,10 @@ struct drm_plane {
struct drm_crtc *crtc;
struct drm_framebuffer *fb;
/* Temporary tracking of the old fb while a modeset is ongoing. Used
* by drm_mode_set_config_internal to implement correct refcounting. */
struct drm_framebuffer *old_fb;
const struct drm_plane_funcs *funcs;
struct drm_object_properties properties;
......@@ -903,6 +907,7 @@ int drm_connector_register(struct drm_connector *connector);
void drm_connector_unregister(struct drm_connector *connector);
extern void drm_connector_cleanup(struct drm_connector *connector);
extern unsigned int drm_connector_index(struct drm_connector *connector);
/* helper to unplug all connectors from sysfs for device */
extern void drm_connector_unplug_all(struct drm_device *dev);
......@@ -942,6 +947,7 @@ extern int drm_plane_init(struct drm_device *dev,
const uint32_t *formats, uint32_t format_count,
bool is_primary);
extern void drm_plane_cleanup(struct drm_plane *plane);
extern unsigned int drm_plane_index(struct drm_plane *plane);
extern void drm_plane_force_disable(struct drm_plane *plane);
extern int drm_crtc_check_viewport(const struct drm_crtc *crtc,
int x, int y,
......
......@@ -77,7 +77,6 @@ struct drm_fb_helper_funcs {
struct drm_fb_helper_connector {
struct drm_connector *connector;
struct drm_cmdline_mode cmdline_mode;
};
struct drm_fb_helper {
......
......@@ -53,6 +53,11 @@ struct drm_modeset_acquire_ctx {
* list of held locks (drm_modeset_lock)
*/
struct list_head locked;
/**
* Trylock mode, use only for panic handlers!
*/
bool trylock_only;
};
/**
......@@ -120,6 +125,17 @@ int drm_modeset_lock_interruptible(struct drm_modeset_lock *lock,
void drm_modeset_unlock(struct drm_modeset_lock *lock);
struct drm_device;
struct drm_crtc;
void drm_modeset_lock_all(struct drm_device *dev);
int __drm_modeset_lock_all(struct drm_device *dev, bool trylock);
void drm_modeset_unlock_all(struct drm_device *dev);
void drm_modeset_lock_crtc(struct drm_crtc *crtc);
void drm_modeset_unlock_crtc(struct drm_crtc *crtc);
void drm_warn_on_modeset_not_all_locked(struct drm_device *dev);
struct drm_modeset_acquire_ctx *
drm_modeset_legacy_acquire_ctx(struct drm_crtc *crtc);
int drm_modeset_lock_all_crtcs(struct drm_device *dev,
struct drm_modeset_acquire_ctx *ctx);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment