#include <iostream>
#include <algorithm>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <map>
#include <iomanip>
#include <set>
#include <ctime>
#include <tuple>
#include <bitset>
#include <assert.h>
#include <deque>
using namespace std;

/*
using PAIR = pair<string, int>;
sort(sp.begin(), sp.end(), [](PAIR l, PAIR r){
        return l.first<r.first || (l.first==r.first && l.second > r.second);
        });
*/

typedef long long ll;

#define fi first
#define se second
#define rep(i, n) for (ll i = 0; i < n; i++)
#define debugA() cerr << "AAAAA" << endl
#define debug_() cerr << "-------------" << endl
#define debug(x) cerr << #x << ": " << x << endl
#define debug_vec(v)                   \
    cout << #v << endl;                \
    for (int i = 0; i < v.size(); i++) \
    {                                  \
        cout << v[i] << " ";           \
    }                                  \
    cout << endl;
#define debug_vec2(v)                         \
    cout << #v << endl;                       \
    for (int i = 0; i < v.size(); i++)        \
    {                                         \
        for (int j = 0; j < v[i].size(); j++) \
        {                                     \
            cout << v[i][j] << " ";           \
        }                                     \
        cout << endl;                         \
    }

using Graph = vector<vector<int>>;
using P = pair<int, int>;
using P1 = pair<int, pair<int, int>>; // クラスカル法とかで、{cost, {from, to}}的に使う。
const ll INF = 1001001001001001001;
const double pi = acos(-1);

// 使い方
// UnionFind tree(N);
// struct UnionFind
// {
//     vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

//     UnionFind(int N) : par(N)
//     { //最初は全てが根であるとして初期化
//         for (int i = 0; i < N; i++)
//             par[i] = i;
//     }

//     int root(int x)
//     { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
//         if (par[x] == x)
//             return x;
//         return par[x] = root(par[x]);
//     }

//     void unite(int x, int y)
//     {                     // xとyの木を併合
//         int rx = root(x); //xの根をrx
//         int ry = root(y); //yの根をry
//         if (rx == ry)
//             return;   //xとyの根が同じ(=同じ木にある)時はそのまま
//         par[rx] = ry; //xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
//     }

//     bool same(int x, int y)
//     { // 2つのデータx, yが属する木が同じならtrueを返す
//         int rx = root(x);
//         int ry = root(y);
//         return rx == ry;
//     }
// };

int main()
{
    int h, w;
    cin >> h >> w;
    vector<string> s(h);
    for (int i = 0; i < h; i++)
    {
        cin >> s[i];
    }
    vector<int> dx = {1, -1, 0, 0};
    vector<int> dy = {0, 0, 1, -1};
    int ans = 0;
    for (int i = 0; i < h; i++)
    {
        for (int j = 0; j < w; j++)
        {
            // s[i][j]がスタート
            if (s[i][j] == '.')
            {
                vector<vector<int>> dist(h, vector<int>(w, -1));
                queue<P> que;
                que.push({i, j});
                dist[i][j] = 0;
                while (!que.empty())
                {
                    int x = que.front().fi;
                    int y = que.front().se;
                    que.pop();
                    for (int k = 0; k < 4; k++)
                    {
                        int nx = x + dx[k];
                        int ny = y + dy[k];
                        if (nx >= 0 && nx < h && ny >= 0 && ny < w && s[nx][ny] == '.' && dist[nx][ny] == -1)
                        {
                            que.push({nx, ny});
                            dist[nx][ny] = dist[x][y] + 1;
                            ans = max(ans, dist[nx][ny]);
                            // debug(ans);
                        }
                    }
                }
                // debug_vec2(dist);
            }
        }
    }
    cout << ans << endl;

    return 0;
}


Measured memory_bytes: 256