using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace AtCoder
{
    class Program
    {
        private static string Read() { return Console.ReadLine(); }
        private static int ReadInt() { return int.Parse(Read()); }
        private static long ReadLong() { return long.Parse(Read()); }
        private static double ReadDouble() { return double.Parse(Read()); }
        private static int[] ReadInts() { return Array.ConvertAll(Read().Split(), int.Parse); }
        private static long[] ReadLongs() { return Array.ConvertAll(Read().Split(), long.Parse); }
        private static double[] ReadDoubles() { return Array.ConvertAll(Read().Split(), double.Parse); }

        class Point
        {
            public readonly int h;
            public readonly int w;
            public Point(int h, int w)
            {
                this.h = h;
                this.w = w;
            }
        }

        const int INF = int.MaxValue / 2;

        static Point Search(Point S, int H, int W, string[] lines, int[][] memo)
        {
            var queue = new Queue<Point>();
            memo[S.h][S.w] = 0;
            queue.Enqueue(S);
            while (queue.Count > 0)
            {
                var p = queue.Dequeue();
                foreach (var q in new[] { new Point(p.h - 1, p.w),
                                          new Point(p.h + 1, p.w),
                                          new Point(p.h, p.w - 1),
                                          new Point(p.h, p.w + 1) })
                {
                    if (0 <= q.h && q.h < H && 0 <= q.w && q.w < W && lines[q.h][q.w] == '.')
                    {
                        if (memo[q.h][q.w] > memo[p.h][p.w] + 1)
                        {
                            memo[q.h][q.w] = memo[p.h][p.w] + 1;
                            queue.Enqueue(q);
                        }
                    }
                }
            }
            var max = new Point(0, 0);
            for (int h = 0; h < H; ++h)
            {
                for (int w = 0; w < W; ++w)
                {
                    if (memo[h][w] < INF && memo[h][w] > memo[max.h][max.w])
                    {
                        max = new Point(h, w);
                    }
                }
            }
            return max;
        }

        static int Solve(int H, int W, string[] lines)
        {
            var P0 = new Point(0, 0);
            for (int h = 0; h < H; ++h)
            {
                for (int w = 0; w < W; ++w)
                {
                    if (lines[h][w] == '.')
                    {
                        P0 = new Point(h, w);
                        break;
                    }
                }
            }

            var memo1 = new int[H][];
            for (int i = 0; i < H; ++i) { memo1[i] = Enumerable.Repeat(INF, W).ToArray(); }
            var P1 = Search(P0, H, W, lines, memo1);

            var memo2 = new int[H][];
            for (int i = 0; i < H; ++i) { memo2[i] = Enumerable.Repeat(INF, W).ToArray(); }
            var P2 = Search(P1, H, W, lines, memo2);

            return memo2[P2.h][P2.w];
        }

        static void Main(string[] args)
        {
            var V = ReadInts();
            int H = V[0], W = V[1];
            var lines = new string[H];
            for (int i = 0; i < H; ++i) { lines[i] = Read(); }
            Console.WriteLine(Solve(H, W, lines));
        }
    }
}
